This morning I stumbled on a complex test to write. The need was to create and show a custom Form (written with Windows Form) that relies on the System.ComponentModel.BackgroundWorker to do initialization stuff without freezing the UI. The test is complex because after creating and showing the form, it must wait somehow to release the UI thread for a while to let the BackgroundWorker achieve the RunWorkerCompleted on the UI thread.
I know that this is something we’ve done in the past and I know this is tricky enough to not reinvent the wheel. But with a test suite of over 13.000 tests this is quite challenging to find where we did that. So I decided to use NDepend querying facility to search.
First I analyze all NDepend assemblies, test assemblies included. Then I generate a code query to match all classes that derive from Form. This can be done from the NDepend Search panel : search Form by name in third-party types and then use a right-click menu to generate the code query:
The CQLinq code query generated is:
1 2 3 4 |
from t in Types let depth0 = t.DepthOfDeriveFrom("System.Windows.Forms.Form") where depth0 >= 0 orderby depth0 select new { t, depth0 } |
60 classes are matched:
Let’s refine this query to match all methods that create any of those form classes.This could be achieved by iterating over (all methods) x (all form classes), but the NDepend.API extension method ThatCreateAny() acts like a join and operates in a linear time. For our search scenario, waiting a few seconds to get a search result is not a problem. But for a code rule written with CQLinq, this is important to run it as fast as possible in a few milliseconds, to run all queries and rules often in Visual Studio within a few seconds, hence the query performance entry on the documentation.
1 2 3 4 5 6 7 8 |
let formTypes = (from t in Types let depth0 = t.DepthOfDeriveFrom("System.Windows.Forms.Form") where depth0 >= 0 select t) from m in Application.Methods.ThatCreateAny(formTypes) select m |
280 methods are instantiating some form classes. Let’s refine the query to match only tests method. The cleanest way would be to check for the usage of TestAttribute, but here just checking for parent assemblies names that contain “Test” is enough:
1 2 3 4 5 6 7 8 |
let formTypes = (from t in Types let depth0 = t.DepthOfDeriveFrom("System.Windows.Forms.Form") where depth0 >= 0 select t) from m in Application.Methods.ThatCreateAny(formTypes) where m.ParentAssembly.Name.Contains("Test") select m |
Still 122 test methods matched.
Before filtering the result even more, let’s refine the query to display for each test the form class(es) it instantiates. This can be achieved with a LINQ range variable formsCreated that we use in the result:
1 2 3 4 5 6 7 8 9 10 |
let formTypes = (from t in Types let depth0 = t.DepthOfDeriveFrom("System.Windows.Forms.Form") where depth0 >= 0 select t) from m in Application.Methods.ThatCreateAny(formTypes) where m.ParentAssembly.Name.Contains("Test") let formsCreated = formTypes.Where(t =>m.CreateA(t)) select new { m, formsCreated } |
We can now browse which form(s) are instantiated by each test:
Finally let’s browse only tests that use some asynchronous related code. Many ways can be used to check for asynchronous usages. The easiest way is certainly to look at methods called by a test method, and check which ones have named related to async stuff. I tried a few words like “Async” “Sync” “Thread” “TimeOut” “Wait”… and “Wait” worked:
1 2 3 4 5 6 7 8 9 10 |
let formTypes = (from t in Types let depth0 = t.DepthOfDeriveFrom("System.Windows.Forms.Form") where depth0 >= 0 select t) from m in Application.Methods.ThatCreateAny(formTypes) where m.ParentAssembly.Name.Contains("Test") let formsCreated = formTypes.Where(t =>m.CreateA(t)) where m.MethodsCalled.Any(m1 => m1.SimpleName.Contains("Wait")) select new { m, formsCreated } |
In the source code of the highlighted test I had everything I needed for my scenario, including a link to a tricky stackoverflow answer that we found years ago. I found what I needed within a few minutes and had a bit of fun. I hope the methodology and the resulting query can be adapted to your advanced search scenarios.