The first symptom I noticed was when some of my unit tests wouldn’t run inside Visual Studio 2017. This wasn’t a simple case where a particular test failed, rather the problem was that the test wouldn’t run at all.
The Output window indicated that something was causing the test process to abort part way through:
I confirmed this by running my command line build and checking the output:
Now we’re getting somewhere; we’ve confirmed that the problem exists outside Visual Studio (which makes it easier to reproduce) and that it’s caused by a
StackOverflowException - we even know which assembly contains the faulty test(s).
Back in Visual Studio, I checked to see if any more diagnostic information was available and found something interesting …
It looks as though at least part of the problem is that something goes wrong when discovering tests, well prior to actually running them.
Usefully, the command line runner for xUnit allows you to run the tests from just one specific class or method. Working through each of the test fixture classes, in turn, I found that
SerializerRoundTripTests+WorkspaceItemSelector was the class with the problem:
-parallel none turns off parallel execution, making the test outcomes more predictable.
Interestingly, I found that every test in that fixture failed, pointing to a problem with the whole class, not just a single test.
Closely reviewing the source for the test fixture class
SerializerRoundTripTests, I discovered a subtle bug in the constructor that caused infinite recursion, blowing out the stack and killing the test process. It seems that text fixture initialization occurs outside the reporting scope for xUnit, so just about any exception aborts the test run.
Predictably, the proximate cause of the bug was a recent change I’d made elsewhere in the project, removing some lazy instantiation.
The root cause of the bug was more fundamental - the constructor of my test fixture was doing too much. I’d forgotten - or simply ignored - the guideline that object constructors should do as little as possible, ideally nothing beyond parameter validation and member initialization.
Moving the heavy lifting out of the constructor and into appropriate private members resolved the problem.