If you have not already seen the brief video on the Smart Unit Tests feature, I urge you to do so; this feature can help you overcome any inertia in getting started writing unit tests. But there is much more to it than what is shown in the video and we intend to complete that picture through this series of blog posts.
Take the situation shown in the video - consider having to make changes to a body of code which happens to have no test coverage. We might want to pin down its behaviour in terms of a unit test suite before starting, but that is easier said than done. We might face several impediments:
- The code (“product code”) might not lend itself to being unit testable. It might have tight dependencies with the external environment that will need to be isolated, and if we cannot spot them we might not even know where to get started from.
- Then there is the quality of the tests. There are many measures of that quality. There is the measure of coverage - how many branches, or code paths, or other program artifacts, in the product code do our tests touch. There is the measure of assertions expressing “is the code doing the right thing?”. Neither of these measures by themselves is sufficient, however. Instead, it would be nice if we had a high density of assertions being validated with high code coverage. But it is not easy to do this kind of quality-analyses in our heads as we write the tests and as a consequence we might end up with tests that exercise the same code paths repeatedly; perhaps just testing the “happy path” even, and we will never know if the product code can even cope with all those edge cases.
- And frustratingly, we might not even know what assertions to put in - imagine being called upon to make changes to an unfamiliar code base!
Smart Unit Tests tries to address these impediments. It discovers all code paths in your code, synthesizes precise test input values that exercise those code paths, and then records what the output from your code was for the said inputs. It then persists these as a compact test suite with high coverage. Here is the result of running it on the sample code in the video:
By default, if you do nothing more than just run it on a piece of code, the test suite captures the observed behaviour of the code for each of the synthesized inputs. At this stage, except for tests causing runtime errors, the remaining are deemed to be passing tests - after all that is the observed behaviour. This is especially useful when dealing with a code base without tests or a test oracle. You can baseline the current/observed behaviour of the code as a suite of tests for use as a regression suite.
Additionally, if you write assertions expressing the correctness properties of the code, then it will come up with test inputs that can validate/falsify the assertions as well (each such input representing a bug in the code, and thereby a failing test).
When the product code changes, rerun Smart Unit Tests, and it will automatically take care of keeping the persisted test suite up to date.
If this sounds interesting stay tuned. In upcoming posts we will discuss these capabilities in detail. In the meantime take a look at the documentation, and report any issues or overall feedback below or through the Send a Smile feature in Visual Studio.