Create your own Test Host using XAML to run your unit tests


A few days ago, somebody came into my office and plopped down a box. It seemed very light. He said that it was a new PC. I thought hmmm…. The box seems empty…Why am I getting a new PC?.



Apparently an inventory was made and my current hardware was at the lower end of the list.


 


So I started up the Dell Optiplex 755 with 4 gigs of RAM, and it was running Vista 64. Super: I hadn’t done much with 64 bit code yet.


 


Sure enough, debugging 64 bit native applications shows that the CPU has 64 bit wide registers (and more registers!). If I attach to a 64 bit process, the Debug->Windows->Registers looks like:


 


RAX = 0000000000000000 RBX = 000000001DEB7818 RCX = 000000000F99A027


RDX = 0000000000000000 RSI = 0000000000000001 RDI = 00000000242E5454


R8  = 000000001FD3FCB8 R9  = 0000000000000000 R10 = 0000000000000000


R11 = 0000000000000206 R12 = 0000000000000000 R13 = 0000000000000000


R14 = 0000000000000000 R15 = 0000000000000000 RIP = 000007FEF0FD3D29


RSP = 000000001FD3FCF0 RBP = 0000000000000000 EFL = 00000246


 


If I attach to a 32 bit process:


 


EAX = 00000000 EBX = 006AC138 ECX = 79E74400 EDX = 00000000 ESI = 00DF6700


EDI = 00000000 EIP = 59B80265 ESP = 001DDEE8 EBP = 001DDEE8 EFL = 00000206


 


001DDEFC = 06CD35C8


 


 


(The debugger is pretty amazing!)


 


The sheer size of the registers means instead of a maximum 2^32 =4 gig virtual address space, we have 2^64


To calculate that, type this in the Fox command window:


?LOG10(2^64)


?2^64


Which shows 19.27, 1.844674E+19


 


That means 19.27 zeros: 18,000,000,000,000,000,000 which is 18 billion billion or 18 exabytes!


 


Coincidentally, we have some new 64 bit code that was recently created by “patching” the 32 bit version, and I wanted some test tools for it.


 


I have a VS Test Project with many (32 bit) tests. (Use Visual Studio Test framework to create tests for your code)


 


So I created my own Test Host in a day:


 


This Test Host:


·         Uses the existing 32 bit test source code as linked files: if you make changes to the tests, they will automatically be updated in both 32 and 64 bit versions


·         Can be built as 32 bit or 64 bit (Project->Properties->Compile->Advanced Compile Options->Target CPU->AnyCPU or x86), so it can run tests in either 32 or 64 bits.  When set as AnyCPU and running on 64 bit OS like Vista 64, then it will run as 64 bit.


·         Does not require test deployment: you can test in place. VSTestHost requires deployment.


·         Uses reflection on itself to get and run the TestInitialize, TestCleanup and TestMethods.


·         Can run selected tests in a loop for stress tests, like leak detection.


·         Uses XAML and Data Binding (see Create your own media browser: Display your pictures, music, movies in a XAML tooltip)


o   Notice how the enabling and disabling of buttons is done via data binding.


o   The data binding updates the status (Success,Failure/Pending) and its color in the ListView.


o   Shows the test methods in a ListView with click/sort column headers.


·         The tests run on a worker thread. The UI is thus responsive and not blocked.


·         A progress bar and stopwatch also show


·         Can be run without UI: another assembly can call it to run tests


·         Can determine if it’s running 64 or 32:  IntPtr.Size=8 bytes on 64, 4 bytes on 32.


 


 


I’m thinking about adding a new 32 bit test case (for running in the VS IDE) that will launch all the 64 bit tests, or a new one for each of the 32 bit test cases, but that’s lower priority.


 


This new test host has already helped me find several 64 bit issues.


 


Follow my prior post: Use Visual Studio Test framework to create tests for your code to create a project.  (If you don’t have Fox or Excel, you can either get them or remove the Fox/Excel code in the test)


 


If you add these lines to the test


        System.Diagnostics.Debug.WriteLine(“Sizeof IntPtr = “ + IntPtr.Size.ToString)


        System.Diagnostics.Debug.WriteLine(System.Diagnostics.Process.GetCurrentProcess.MainModule.FileName)


 


You’ll get the output:


Sizeof IntPtr = 4


C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\vstesthost.exe


 


Nothing unexpected. VSTestHost is a 32 bit EXE, which means only 32 bit code is run.


 


 


To make the test host more interesting, add a few more tests to the bottom of UnitTests1:


    Shared oRandom As New Random


 


    <TestMethod()> _


    Sub EmptyTest()


 


    End Sub


 


    <TestMethod()> _


    Sub AlwaysFail()


        Assert.IsTrue(False, “True is never false!”)


    End Sub


    <TestMethod()> _


    Sub SometimesFail()


        If oRandom.NextDouble < 0.4 Then


            Assert.IsTrue(False, “True is never false!”)


        End If


    End Sub


    <TestMethod()> _


    Sub RarelyFails()


        If oRandom.NextDouble < 0.02 Then


            Assert.IsTrue(False, “True is rarely false!”)


        End If


    End Sub


 


 


 


 


 


To create the Test Host:


 


Right Click on the Solution from above in Solution explorer, choose Add->New Project. This time choose Windows->WPF Application. Put in a folder next to the folder of the existing test project. I called mine MyTestHost


 


Right Click on MyTestHost in solution explorer, choose Set As Startup Project. That way, hitting F5 will run this new project.


Delete the Window1.XAML in the solution explorer. We’ll create our own.


 


Delete MyTestHost->Application.xaml  (we have our own Sub Main)


 


Right Click on MyTestHost in solution explorer, choose Add New Item, Code->Class. Name it MyTestHost.vb


 


Right Click on MyTestHost in solution explorer, choose Add Existing Item, navigate to the UnitTest1.vb file in the other project. Make sure you add as a Lnked File. The “Add” button on the dialog has a little down arrow that allows you to choose Add As Link.


 


Note: the Fox code in the unit test TestMethod1 will fail in 64 because there is no 64 bit version of it.


 


Add references to:


Microsoft.VisualStudio.QualityTools.UnitTestFramework


System.Windows.Forms


 


There are 3 sections of code to paste below: Window1.XAML, Window1.XAML.VB (the code behind file), and MyTestHost.vb


 


Hit F5, click on the button  to run the tests.


You can force the Testmethod1 to fail simply by forcing Excel to close before it’s done.


 


 


See also:


Remove double spaces from pasted code samples in blog


 


 


 


<Window1.XAML code to paste>


<Window x:Class=”Window1″


    xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation”


    xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml”


       


    Title=”Window1″ Height=”711″ Width=”1043″>


    <Grid DataContext=”{Binding}”>


 


        <TextBlock Height=”21″ HorizontalAlignment=”Right” Margin=”0,12,16,0″ Name=”tbClock” VerticalAlignment=”Top” Width=”81″ Text=”{Binding Path=Clock}”/>


        <ListView Margin=”22,49,54,90″ Name=”ListView1″ ItemsSource=”{Binding Path=TestMethods}” >


            <ListView.View>


            <GridView >


               


            <GridViewColumn>Selected


                <GridViewColumn.CellTemplate>


                    <DataTemplate>


                        <CheckBox Name=”ChkBox” IsChecked=”{Binding Path=Selected}”></CheckBox>


                    </DataTemplate>


                </GridViewColumn.CellTemplate>


            </GridViewColumn>


            <GridViewColumn>Status


                <GridViewColumn.CellTemplate>


                    <DataTemplate>


                        <TextBlock Width=”50″ Text=”{Binding Path=Status}”  Foreground=”{Binding Path=StatusColor}”></TextBlock>


                    </DataTemplate>


                </GridViewColumn.CellTemplate>


            </GridViewColumn>


            <GridViewColumn>TestName


                <GridViewColumn.CellTemplate>


                    <DataTemplate>


                        <TextBlock Text=”{Binding Path=TestName}”></TextBlock>


                    </DataTemplate>


                </GridViewColumn.CellTemplate>


            </GridViewColumn>


            <GridViewColumn>Description


                <GridViewColumn.CellTemplate>


                    <DataTemplate>


                        <TextBlock Text=”{Binding Path=Description}”></TextBlock>


                    </DataTemplate>


                </GridViewColumn.CellTemplate>


            </GridViewColumn>


            <GridViewColumn>TestClass


                <GridViewColumn.CellTemplate>


                    <DataTemplate>


                        <TextBlock Text=”{Binding Path=TestClass}”></TextBlock>


                    </DataTemplate>


                </GridViewColumn.CellTemplate>


            </GridViewColumn>


            <GridViewColumn>mSecs


                <GridViewColumn.CellTemplate>


                    <DataTemplate>


                        <TextBlock Text=”{Binding Path=mSecs}” Width=”40″ HorizontalAlignment=”Right”></TextBlock>


                    </DataTemplate>


                </GridViewColumn.CellTemplate>


            </GridViewColumn>


            <GridViewColumn>ErrorMessage


                <GridViewColumn.CellTemplate>


                    <DataTemplate>


                        <TextBlock Text=”{Binding Path=ErrorMessage}” ToolTip=”{Binding Path=ErrorMessage}”></TextBlock>


                    </DataTemplate>


                </GridViewColumn.CellTemplate>


            </GridViewColumn>


            <GridViewColumn>Owner


                <GridViewColumn.CellTemplate>


                    <DataTemplate>


                        <TextBlock Text=”{Binding Path=Owner}”></TextBlock>


                    </DataTemplate>


                </GridViewColumn.CellTemplate>


            </GridViewColumn>


            <GridViewColumn>Pri


                <GridViewColumn.CellTemplate>


                    <DataTemplate>


                        <TextBlock Text=”{Binding Path=Pri}”></TextBlock>


                    </DataTemplate>


                </GridViewColumn.CellTemplate>


            </GridViewColumn>


               


            </GridView>


            </ListView.View>


 


        </ListView>


        <Button Height=”23″ HorizontalAlignment=”Left” Margin=”22,0,0,35″


                Name=”btnSelectAll” VerticalAlignment=”Bottom” Width=”85″ ButtonBase.Click=”btnClicked”>Select_All</Button>


        <Button Height=”23″ HorizontalAlignment=”Left” Margin=”117,0,0,35″


                Name=”btnInvertSelection” VerticalAlignment=”Bottom” Width=”85″ ButtonBase.Click=”btnClicked”>_InvertSelection</Button>


        <CheckBox Height=”16″ HorizontalAlignment=”Left” Margin=”215,0,0,65″


                  Name=”chkLoopForever” VerticalAlignment=”Bottom” Width=”120″ IsEnabled=”{Binding Path=NotIsTestRunning}”>_LoopForever</CheckBox>


        <Button Height=”23″ HorizontalAlignment=”Left” Margin=”215,0,0,35″


                Name=”btnRunSelected” VerticalAlignment=”Bottom” ButtonBase.Click=”btnClicked”  Width=”85″ IsEnabled=”{Binding Path=NotIsTestRunning}”>_RunSelected</Button>


        <Button Height=”23″ HorizontalAlignment=”Left” Margin=”315,0,0,35″


                Name=”btnAbortRun” VerticalAlignment=”Bottom” ButtonBase.Click=”btnClicked” IsEnabled=”{Binding Path=IsTestRunning}” Width=”56.82″>_AbortRun</Button>


        <Button Height=”23″ HorizontalAlignment=”Right” Margin=”0,0,54,35″


                Name=”btnQuit” VerticalAlignment=”Bottom” Width=”85″ ButtonBase.Click=”btnClicked” IsCancel=”True”>_Quit</Button>


        <ProgressBar Height=”14″ HorizontalAlignment=”Right” Margin=”0,0,16,0″


                Name=”ProgressBar1″ VerticalAlignment=”Top” Width=”123″  />


 


    </Grid>


</Window>


 


</Window1.XAML code to paste >


 


 


<Window1.XAML.vb code to paste >


Imports System.ComponentModel


Imports System.Collections.ObjectModel


 


Class Window1


    Implements INotifyPropertyChanged   ‘ so XAML UI updates


 


    Private m_TestMethods As ReadOnlyCollection(Of TestItem)


    Private m_SelectedTests As New Collection(Of TestItem)


    Private m_StatusText As String


    Private m_IsTestRunning As Boolean = False


    Private m_Clock As String


    Private m_nTestPass As Integer


    Friend StopWatch As Diagnostics.Stopwatch


    Public Property Clock() As String


        Get


            Return m_Clock


        End Get


        Set(ByVal value As String)


            If m_Clock <> value Then


                m_Clock = value


                RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(“Clock”))


            End If


        End Set


    End Property


    Public Property IsTestRunning() As Boolean


        Get


            Return m_IsTestRunning


        End Get


        Set(ByVal value As Boolean)


            m_IsTestRunning = value


            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(“IsTestRunning”))


            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(“NotIsTestRunning”))


        End Set


    End Property


    Public ReadOnly Property NotIsTestRunning() As Boolean


        Get


            Return Not m_IsTestRunning


        End Get


    End Property


    Public Property StatusText() As String


        Get


            Return m_StatusText


        End Get


        Set(ByVal value As String)


            Dim strTitle = “TestHost “


            If IntPtr.Size > 4 Then


                strTitle += “amd64 64 bit”


            Else


                strTitle += “x86 32 bit”


            End If


            strTitle += ” Total Tests = “ + TestMethods.Count.ToString


            If m_nTestPass > 0 Then


                strTitle += ” Test Pass #” + m_nTestPass.ToString + ” PeakWorkingSet =” + Process.GetCurrentProcess.PeakWorkingSet64.ToString(“n0”)


            End If


            m_StatusText = strTitle + ” “ + value


            Me.Title = m_StatusText    ‘ in case of process crash, we can still see window title


            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(“StatusText”))


        End Set


    End Property


    Public ReadOnly Property TestMethods() As ReadOnlyCollection(Of TestItem)


        Get


            Return m_TestMethods


        End Get


    End Property


    Sub New(ByVal TestMethods As ReadOnlyCollection(Of TestItem))


        InitializeComponent()


        m_TestMethods = TestMethods


        StatusText = “” ‘ init window title


        Me.ProgressBar1.Visibility = Windows.Visibility.Hidden


        Me.ProgressBar1.Minimum = 0


        Me.ProgressBar1.Maximum = 100


        Me.ProgressBar1.Value = 0


 


        Me.DataContext = Me ‘ so XAML can databind to our members


        AddHandler Me.Closed, AddressOf OnWindowClosed


        Me.AddHandler(System.Windows.Controls.GridViewColumnHeader.ClickEvent, New RoutedEventHandler(AddressOf OnHeaderClicked))


    End Sub


    Sub OnWindowClosed(ByVal sender As Object, ByVal e As EventArgs)


        TestItem.ShutDown()  ‘ abort thread


    End Sub


    Private Sub btnClicked(ByVal sender As System.Windows.Controls.Button, ByVal e As RoutedEventArgs)


        Debug.WriteLine((New StackTrace).GetFrame(0).GetMethod.Name + ” “ + sender.Name)


        Select Case CStr(sender.Name)


            Case “btnSelectAll”


                For Each lstItem In ListView1.Items


                    Dim tstItem = CType(lstItem, TestItem)


                    If Not tstItem.Selected Then


                        tstItem.Selected = True


                    End If


                Next


 


            Case “btnInvertSelection”


                For Each lstItem In ListView1.Items


                    Dim tstItem = CType(lstItem, TestItem)


                    tstItem.Selected = Not tstItem.Selected


                Next


            Case “btnRunSelected”


                m_SelectedTests.Clear()


                m_nTestPass = 1


                For Each lstItem In ListView1.Items ‘ Get the user selected items in display order


                    Dim tstItem = CType(lstItem, TestItem)


                    tstItem.Reset() ‘ reset all to not run yet (doesn’t reset checkbox)


                    If tstItem.Selected Then


                        m_SelectedTests.Add(tstItem)


                        tstItem.Status = TestItem.TstStatus.Pending


                    End If


                Next


                If Me.chkLoopForever.IsChecked Then


                    Do While True


                        DoRunSelected()


                        For Each lstItem In ListView1.Items


                            Dim tstItem = CType(lstItem, TestItem)


                            If tstItem.Status = TestItem.TstStatus.Aborted OrElse tstItem.Status = TestItem.TstStatus.Failed Then


                                Me.StatusText = String.Format(” Failed  {0}”, DateTime.Now.ToString)


                                Exit Do


                            End If


                            tstItem.Reset()


                        Next


                        m_nTestPass += 1


                    Loop


                Else


                    DoRunSelected()


                End If


 


            Case “btnAbortRun”


                TestItem.ShutDown()


                For Each tstItem In m_SelectedTests


                    If tstItem.Status = TestItem.TstStatus.Pending Then


                        tstItem.Status = TestItem.TstStatus.NotRun


                    End If


                Next


            Case “btnQuit”


                TestItem.ShutDown()


                Me.Close()


            Case Else


                Debug.Assert(False, “Unhandled button”)


        End Select


    End Sub


    Private Sub DoRunSelected()


        IsTestRunning = True


        Dim nTestsRun = 0


        Dim nTestsFailed = 0


        StopWatch = Diagnostics.Stopwatch.StartNew


        Me.StatusText = String.Format(“{0} Test run ({1}/{2}) “, DateTime.Now.ToString, nTestsRun, m_SelectedTests.Count)


        Try


            For Each lstItem In ListView1.Items


                Dim tstItem = CType(lstItem, TestItem)


                If tstItem.Selected Then


                    Me.ProgressBar1.Visibility = Windows.Visibility.Visible


 


                    tstItem.RunTest(Me)   ‘ actually run the test


                    Me.ProgressBar1.Visibility = Windows.Visibility.Hidden


                    nTestsRun += 1


                    If tstItem.Status = TestItem.TstStatus.Failed Then


                        nTestsFailed += 1


                    End If


                    Me.StatusText = String.Format(“{0} Test run {1}/{2}  #Failed = {3}”, DateTime.Now.ToString, nTestsRun, m_SelectedTests.Count, nTestsFailed)


                    If tstItem.Status = TestItem.TstStatus.Aborted Then


                        Exit For


                    End If


                End If


 


            Next


        Catch ex As Exception


        End Try


        Me.StatusText = String.Format(  Done: {0:f2} secs”, StopWatch.ElapsedMilliseconds / 1000)


        IsTestRunning = False


 


    End Sub


    Private Sub ListBox1_KeyUp(ByVal sender As Object, ByVal args As System.Windows.Input.KeyEventArgs) Handles ListView1.KeyUp


        If args.Key = Key.Space Then


            Dim lb As System.Windows.Controls.ListBox = CType(sender, System.Windows.Controls.ListBox)


            If lb.SelectedIndex >= 0 Then


                Dim lbi = CType(lb.ItemContainerGenerator.ContainerFromIndex(lb.SelectedIndex), System.Windows.Controls.ListBoxItem)


                If lbi IsNot Nothing Then


                    Dim tstItem = CType(lbi.Content, TestItem)


                    tstItem.Selected = Not tstItem.Selected


 


                End If


            End If


        End If


    End Sub


    ‘see http://blogs.msdn.com/calvin_hsia/archive/2007/11/29/6600915.aspx


    Dim m_LastDir As System.ComponentModel.ListSortDirection = System.ComponentModel.ListSortDirection.Ascending


    Dim m_LastHeaderClicked As System.Windows.Controls.GridViewColumnHeader = Nothing


    Private Sub OnHeaderClicked(ByVal sender As Object, ByVal e As RoutedEventArgs)


        If e.OriginalSource.GetType Is GetType(System.Windows.Controls.GridViewColumnHeader) Then


 


            Dim direction = System.ComponentModel.ListSortDirection.Ascending


            Dim gvHdr = CType(e.OriginalSource, System.Windows.Controls.GridViewColumnHeader)


            If gvHdr IsNot Nothing AndAlso gvHdr.Column IsNot Nothing Then


                Dim hdr As String


                hdr = CStr(gvHdr.Column.Header)


                If gvHdr Is m_LastHeaderClicked Then


                    If m_LastDir = System.ComponentModel.ListSortDirection.Ascending Then


                        direction = System.ComponentModel.ListSortDirection.Descending


                    End If


                End If


                Sort(hdr, direction)


                m_LastHeaderClicked = gvHdr


                m_LastDir = direction


            End If


 


        End If


 


    End Sub


    Private Sub Sort(ByVal sortby As String, ByVal dir As System.ComponentModel.ListSortDirection)


        Me.ListView1.Items.SortDescriptions.Clear()


        Dim sd = New System.ComponentModel.SortDescription(sortby, dir)


        Me.ListView1.Items.SortDescriptions.Add(sd)


        Me.ListView1.Items.Refresh()


    End Sub


 


 


    Public Event PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) _


        Implements System.ComponentModel.INotifyPropertyChanged.PropertyChanged


 


End Class


 


</Window1.XAML.vb code to paste >


 


 


<MyTestHost.vb code to paste >


Imports System.Reflection


Imports System.Windows


Imports System.Collections.ObjectModel


Imports System.Windows.Controls


Imports System.ComponentModel


Imports Microsoft.VisualStudio.TestTools.UnitTesting


 


 


Public Class MyTestHost


    Private Shared m_TestMethods As ReadOnlyCollection(Of TestItem)


 


    Shared Sub Main()


        m_TestMethods = GetTestMethods()


        If System.Environment.GetCommandLineArgs.Count > 1 Then ‘ arg 0 is the fullpath name of the app


            Dim testname = System.Environment.GetCommandLineArgs(1)


        Else


            ‘ show UI


            Dim wpfApplicationWindow As System.Windows.Window = New Window1(m_TestMethods)


            wpfApplicationWindow.ShowDialog()


 


        End If


 


    End Sub


    Public Shared Function GetTestMethods() As ReadOnlyCollection(Of TestItem)  ‘ can be called from other assemblies


        Dim retval As New Collection(Of TestItem)


        Dim MyAsm = Assembly.GetExecutingAssembly


 


        Dim TestClasses = From typ In MyAsm.GetTypes Where typ.GetCustomAttributes(GetType(TestClassAttribute), True).Length > 0


        For Each TestClassName In TestClasses


            Debug.WriteLine(“Test class “ + TestClassName.Name)


 


            Dim TestClassInstance = MyAsm.CreateInstance(TestClassName.FullName)


            Dim TestMethods = From meth In TestClassInstance.GetType.GetMethods _


                        Where meth.GetCustomAttributes(GetType(TestMethodAttribute), True).Length > 0 _


                            AndAlso Not meth.GetCustomAttributes(GetType(IgnoreAttribute), True).Length > 0


 


            Debug.WriteLine(“Count=” + TestMethods.Count.ToString)


 


            ‘ you can define your own TestContext class


            ‘Dim tstContext = New VBTest64TestContext


            ‘Dim TestContext_Property As PropertyInfo = TestClassInstance.GetType.GetProperty(“TestContext”)


            ‘If TestContext_Property IsNot Nothing Then


                TestContext_Property.SetValue(TestClassInstance, tstContext, BindingFlags.SetProperty, Nothing, Nothing, Nothing)


            ‘End If


 


            Dim TestInitialize_method As MethodInfo = TestClassInstance.GetType.GetMethod(“TestInitialize”)


            Dim TestCleanup_method As MethodInfo = TestClassInstance.GetType.GetMethod(“TestCleanup”)


 


            For Each meth In TestMethods ‘ In From dd In meths Where dd.Name.StartsWith(“PepperQu”)


                Debug.WriteLine(meth.Name)


                Dim TestDesc = “”


                Dim TestOwner = “”


                Dim TestPriority = “”


 


                Dim Tmp = meth.GetCustomAttributes(GetType(Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute), True)


                If Tmp.Length > 0 Then


                    TestDesc = CType(Tmp(0), Microsoft.VisualStudio.TestTools.UnitTesting.DescriptionAttribute).Description


                End If


 


                Tmp = meth.GetCustomAttributes(GetType(OwnerAttribute), True)


                If Tmp.Length > 0 Then


                    TestOwner = CType(Tmp(0), OwnerAttribute).Owner


                End If


 


                Tmp = meth.GetCustomAttributes(GetType(PriorityAttribute), True)


                If Tmp.Length > 0 Then


                    TestPriority = CType(Tmp(0), PriorityAttribute).Priority.ToString


                End If


 


                retval.Add(New TestItem(meth.Name, TestClassName.FullName, TestDesc, _


                                            TestOwner, TestPriority, meth, _


                                            TestClassInstance, TestInitialize_method, TestCleanup_method))


 


            Next


 


        Next


 


        Return New ReadOnlyCollection(Of TestItem)(retval)


 


    End Function


End Class


 


Public Class TestItem


    Implements INotifyPropertyChanged


    Enum TstStatus


        Idle


        Pending


        Running


        Passed


        Aborted


        NotRun


        Failed


    End Enum


    Private m_TestName As String


    Private m_TestClass As String


    Private m_Description As String


    Private m_TestOwner As String


    Private m_TestPriority As String


    Private m_Selected As Boolean = True  ‘ default to checkbox selected


    Private m_TestStatus As TstStatus = TstStatus.Idle


    Private m_TestInitialize_method As MethodInfo


    Private m_TestCleanup_method As MethodInfo


    Private m_TestMethodInfo As MethodInfo  ‘ the actual test to run


    Private m_TestClassInstance As Object


    Private m_ElapsedTime As String


    Private m_ErrorMessage As String


    Private m_Stopwatch As Diagnostics.Stopwatch


 


    Private Shared m_bgThread As System.Threading.Thread


    Private Shared m_threadRunning As Boolean


    Delegate Sub delThreadDone()


    Private m_ThreadDoneEvent As System.Threading.ManualResetEvent


 


    Sub New(ByVal TestName As String, ByVal TestClass As String, ByVal testDesc As String, _


            ByVal TestOwner As String, ByVal TestPriority As String, _


            ByVal meth As MethodInfo, ByVal TestClassInstance As Object, _


            ByVal TestInitialize_method As MethodInfo, ByVal TestCleanup_method As MethodInfo)


        m_TestName = TestName


        m_TestClass = TestClass


        m_Description = testDesc


        m_TestOwner = TestOwner


        m_TestPriority = TestPriority


        m_TestMethodInfo = meth


        m_TestInitialize_method = TestInitialize_method


        m_TestCleanup_method = TestCleanup_method


        m_TestClassInstance = TestClassInstance


    End Sub


    Shared Sub ShutDown()


        If m_bgThread IsNot Nothing Then


            m_bgThread.Abort()


            Do While m_threadRunning


                System.Windows.Forms.Application.DoEvents()


            Loop


        End If


    End Sub


 


    Sub BgThreadProc(ByVal disp As Object)


        m_Stopwatch = Stopwatch.StartNew


        Dim stat As TstStatus = TstStatus.Running


        Status = TstStatus.Running


        Dim deldone As New delThreadDone(AddressOf BgThreadDone)


        Dim fDidRunCleanup = False


        Debug.WriteLine(TestName)


        Try


            If m_TestInitialize_method IsNot Nothing Then


                m_TestInitialize_method.Invoke(m_TestClassInstance, BindingFlags.InvokeMethod, Nothing, Nothing, Nothing)


            End If


 


            ‘ run the test


            m_TestMethodInfo.Invoke(m_TestClassInstance, Reflection.BindingFlags.InvokeMethod, Nothing, Nothing, Nothing)


 


            If m_TestCleanup_method IsNot Nothing Then


                m_TestCleanup_method.Invoke(m_TestClassInstance, BindingFlags.InvokeMethod, Nothing, Nothing, Nothing)


                fDidRunCleanup = True


            End If


            stat = TstStatus.Passed


 


        Catch ex As Exception ‘When ex.GetType IsNot GetType(System.Threading.ThreadAbortException)


            If ex.GetType Is GetType(System.Threading.ThreadAbortException) Then


                If Not fDidRunCleanup Then


                    If m_TestCleanup_method IsNot Nothing Then


                        m_TestCleanup_method.Invoke(m_TestClassInstance, BindingFlags.InvokeMethod, Nothing, Nothing, Nothing)


                        fDidRunCleanup = True


                    End If


                End If


                Status = TstStatus.Aborted


                ErrorMessage = “User aborted test run”


                CType(disp, System.Windows.Threading.Dispatcher).Invoke(Threading.DispatcherPriority.Background, deldone)


                Throw ex


            End If


            Debug.WriteLine(String.Format(“Got exception {0} {1}”, m_TestMethodInfo.Name, ex.InnerException.Message))


            ErrorMessage = ex.InnerException.Message


            stat = TstStatus.Failed


 


        End Try


        Me.Status = stat


        mSecs = m_Stopwatch.ElapsedMilliseconds.ToString


        m_Stopwatch.Stop()


        Debug.WriteLine(TestName + ” Done”)


 


        CType(disp, System.Windows.Threading.Dispatcher).Invoke(Threading.DispatcherPriority.Background, deldone)


    End Sub


    Sub BgThreadDone()  ‘ runs on main thread


        m_bgThread = Nothing


        m_threadRunning = False


    End Sub


 


    Friend Sub RunTest(ByVal mWindow As Window1)


        m_ThreadDoneEvent = New System.Threading.ManualResetEvent(False)


 


        m_bgThread = New System.Threading.Thread(AddressOf BgThreadProc) ‘ cant’ use BackgroundWorker because need STA


        m_bgThread.Name = “MyTestHost runtests”


        m_bgThread.SetApartmentState(System.Threading.ApartmentState.STA)


        m_threadRunning = True


        m_bgThread.Start(System.Windows.Threading.Dispatcher.CurrentDispatcher)


        Do While m_threadRunning


            If mWindow IsNot Nothing Then


                Dim pbar As ProgressBar = mWindow.ProgressBar1


                pbar.Value = DateTime.Now.Millisecond / 10


                mWindow.Clock = (mWindow.StopWatch.ElapsedMilliseconds / 1000).ToString(“f1”)


            End If


 


            System.Windows.Forms.Application.DoEvents()


        Loop


    End Sub


    Public Sub Reset()


        mSecs = “”


        ErrorMessage = “”


        Status = TstStatus.Idle


    End Sub


    Public Property ErrorMessage() As String


        Get


            Return m_ErrorMessage


        End Get


        Set(ByVal value As String)


            m_ErrorMessage = value


            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(“ErrorMessage”))


        End Set


    End Property


    Public Property mSecs() As String


        Get


            Return m_ElapsedTime


        End Get


        Set(ByVal value As String)


            m_ElapsedTime = value


            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(“mSecs”))


        End Set


    End Property


    Public Property Status() As TstStatus


        Get


            Return m_TestStatus


        End Get


        Set(ByVal value As TstStatus)


            If value <> m_TestStatus Then


                m_TestStatus = value


                RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(“Status”))


                RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(“StatusColor”))


            End If


        End Set


    End Property


    Public ReadOnly Property StatusColor() As String


        Get


            Select Case m_TestStatus


                Case TstStatus.Failed


                    Return “Red”


                Case TstStatus.Idle


                    Return “LightBlue”


                Case TstStatus.Pending


                    Return “Purple”


                Case TstStatus.Aborted


                    Return “Red”


                Case TstStatus.Passed


                    Return “Green”


                Case TstStatus.NotRun


                    Return “Black”


                Case Else


                    Return “Blue”


 


            End Select


 


        End Get


    End Property


    Public ReadOnly Property TestName() As String


        Get


            Return m_TestName


        End Get


    End Property


    Public ReadOnly Property TestClass() As String


        Get


            Return m_TestClass


        End Get


    End Property


    Public ReadOnly Property Description() As String


        Get


            Return m_Description


        End Get


    End Property


    Public ReadOnly Property Owner() As String


        Get


            Return m_TestOwner


        End Get


    End Property


    Public ReadOnly Property Pri() As String


        Get


            Return m_TestPriority


        End Get


    End Property


    Public Property Selected() As Boolean


        Get


            Return m_Selected


        End Get


        Set(ByVal value As Boolean)


            m_Selected = value


            RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(“Selected”))


        End Set


    End Property


    Public Overrides Function ToString() As String


        Return String.Format(“{0,-20} {1}”, m_TestName.PadRight(20), m_TestClass)


    End Function


 


    Public Event PropertyChanged(ByVal sender As Object, ByVal e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged


End Class


 


 


</MyTestHost.vb code to paste >


 


 


 


End of blog entry


 


 


 


 

Comments (8)

  1. Greg says:

    I’d suggest that this functionality be added to VS’s support for unit testing so that this functionality is supported and the test cases can be maintained over 5+ years of use.

  2. My prior post ( Create your own Test Host using XAML to run your unit tests ) shows how to create a form

  3. Writing programs using .Net is very productive. One reason is because much of memory management is “managed”

  4. What does it mean to make code more maintainable? Certainly obfuscated code is hard to understand, by