Last month in Dynamically create huge tooltips in WPF TreeView and ListView I showed some code that creates large tooltips to present lots of data.
Today, we’ll talk about creating automatic tests for this feature. Testing User Interface features has been difficult historically. How do you automate a mouse hovering over an item?
Having good automated tests is a great defensive mechanism to ensure that future code changes don’t break features. Or, another way to say it, protect features’ future, from your teammates as well as yourself.
In fact, when writing code, I often start with writing a test first (Test Driven Development, or TDD). It usually takes many steps to exercise the code you’re writing: Start the application (or navigate to a web site), navigate the menus to a dialog, choose some options, etc. Code development is an iterative process, so you’ll need to execute the same set of repetitive steps numerous times to exercise your code. Worse, anyone testing the code will need to do the same steps. When you use TDD, the set of repetitive steps is automatic. Also, you can bypass exercising the menu system or dialogs by configuring directly from the test code.
The test code is a living document whose lifetime is as long as the test target.
How many times have you encountered a new code base and had to make a change? The test code not only helps protect existing features, but it also helps you to understand how the code works. You can just single step a test as it executes the code.
Visual Studio even allows you to create Build Definitions that automatically build your code and run the tests, and failing the build if the tests fail. (Use Your Build System to Work with Tests )
First, run the code from the last post Dynamically create huge tooltips in WPF TreeView and ListView
Navigate to a picture or movie file and observe the tooltip shows the picture or movie.
(If you only see a little tiny tooltip show over a JPG, and you’re running on Win2k8 R2 server, you need to enable the Desktop Experience:
Server Manager->Features->Add Features->Desktop Experience.)
Right click on any code in the On_MouseMove method and choose Create Unit tests. Just hit enter to the dialogs to accept the default method to test, and the test project to create, and the TestProject1 name
(You can do many variations, such as use C# to test a VB project and vice versa)
If you get a dialog asking to add a “InternalsVisibleTo attribute”, just say yes.
A file and class named MainWindow_MyTreeViewTest with a test method on_MouseMoveTest is created for you.
Open the test list window: Main Menu->Test->Windows->Test List Editor to show the tests. (I like to Dock this window which can have dozens of tests.)
The hardest thing to do now is to hit Control R-T while the cursor is in the test code method. For years, I’ve been hitting the control key and then hitting RT and starting a much slower debug session for all my tests rather than running a single test.
Upon closer examination, the Test menu has 2 submenus: Run and Debug. You can only see one of these menus at a time, and “Tests in Current Context” appears to have the same shortcut key Ctrl+R,T. I used to think, how can they have the same short cut key? More scrutiny reveals that the keys do indeed have a subtle difference in execution: “Ctrl+R, Ctrl+T” vs “Ctrl+R, T”. Thus release the control key before hitting “T” J
The Test Results window indicates the status of the test as it runs. The default generated test just shows the test result “ Inconclusive” rather than Passed or Failed. Try double clicking on a test result, whether it passed or failed.
Put a breakpoint on a line in the test, then Debug it using Ctrl+R ,Ctrl+T and smile when it worksJ
Edit and continue even work.
Now replace the method on_MouseMoveTest with the sample below. (The default test code is commented out in the code sample.)
(You’ll probably also need the TestCleanup code below: the test may pass but the test run may fail.)
Put a JPG file in your Documents folder so the test code can find it, and run the test.
Now you have a defensive mechanism that protects your feature’s future!
'''A test for on_MouseMove
Public Sub on_MouseMoveTest()
Dim mainWind = New MainWindow
mainWind.Activate() ' so you can see it
Dim treevw = CType(mainWind.Content, MainWindow.MyTreeView)
' now look for a JPG file
Dim tblk = (From tvitem As TreeViewItem In treevw.Items
Let tblock = CType(tvitem.Header, TextBlock)
Let ext = System.IO.Path.GetExtension(tblock.Text).ToLower
Where ext = ".jpg"
Assert.IsNotNull(tblk, "Could not find jpg")
Assert.IsNull(treevw._LastTipObj, "We start with tip = null")
'raise a mouse move event from the treeview item's textblock
' simulating a mouse over the textblock
Mouse.PrimaryDevice, 0) With
.RoutedEvent = TreeViewItem.MouseMoveEvent,
.Source = tblk}
'we need to delay so the rendering thread can actually show the tooltip
' we can't just sleep here, because sleep suspends the main
' thread which tells the rendering thread what to do
Dim start = DateTime.Now
Do Until (DateTime.Now - start).Duration > TimeSpan.FromMilliseconds(3000)
Assert.IsNotNull(treevw._LastTipObj, "we should have a tip object")
Dim tip As ToolTip = treevw._LastTipObj.ToolTip
Assert.IsNotNull(tip, "the tip object's tooltip is null")
Dim mediaelem As MediaElement = tip.Content
Assert.IsTrue(mediaelem.Source.OriginalString = tblk.Text, "The media source must be textblock " + tblk.Text)
'Dim target As MainWindow.MyTreeView = New MainWindow.MyTreeView() ' TODO: Initialize to an appropriate value
'Dim sender As Object = Nothing ' TODO: Initialize to an appropriate value
'Dim e As MouseEventArgs = Nothing ' TODO: Initialize to an appropriate value
'Assert.Inconclusive("A method that does not return a value cannot be verified.")
' The individual test may pass, but the test run may fail
' we need to let garbage collection collect and various threads shut down