January 15, 2018
Refactoring to the xUnit Pattern16 days left to get my spiffy on-site Unit Testing training workshop at half-price. It's jam-packed with unit testy goodness. Here's a little taste of the kind of stuff we cover.
In the introductory part of the workshop, we look at the anatomy of unit test suites and see how - from the most basic designs - we eventually arrive by refactoring at the xUnit design pattern for unit testing frameworks.
If you've been programming for a while, there's a good chance you've written test code in a Main() method, like this:
This saves us the bother of having to run an entire application to get quick feedback while we're adding or changing code in, say, a library.
Notice that there are three components to this test:
Arrange - we set up the object(s) we're going to use to be in the initial state we need for this particular test
Act - we invoke the method we want to test
Assert - We ask questions about the final state of our test object(s) to see if the action has had the desired effect
Of course, a real-world application might need hundreds or even thousands of such tests. Our Main() method is going to get pretty big and unwieldy if we keep adding more and more test cases.
So we can break it down into multiple test methods, one for each test case. The name of each test method can clearly describe what the test is.
Our original Main() method just calls all of our test methods.
But still, when there are hundreds or thousands of test methods, we can end up with one ginormous class. That too can be broken down, grouping related test methods (e.g., all the tests for a bank account) into smaller test fixtures.
Note that each test fixture has a method that invokes all of its test methods, so our original main method doesn't need to invoke them all itself.
This is a final piece of the unit testing jigsaw: the class that tells all of our test fixtures to run their tests. We call this a test suite.
At the most basic level, this simple design gives us the ability to write, organise and run large numbers of tests quickly.
As time goes on, we may add a few bells and whistles to streamline the process and make it more useful and usable.
For example, in our current design, when an assertion fails (using .NET's built-in Debug.Assert() method), it will halt execution. If the first test fails in a suite of 1,000 tests, it won't run the other 999. So we might write our own assertion methods to check and report test failures without halting execution.
And we might want to make the output more user friendly and display more helpful results, so we may add a custom formatter/reporter to write out test results.
And - I can attest from personal experience - it can be a real pain the you-know-what to have to remember to write code to invoke every test method on every test fixture. So we might create a custom test runner - not just a Main() method - that automates the process of test discovery and execution.
We could, for example, invert the dependencies in our test suite on individual test fixtures by extracting a common interface that all fixtures must implement for running its tests. Then we could use reflection or search through the source code for all classes that implement that interface and build the suite automatically.
Likewise, we could specify that test methods must have a specific signature (e.g., start with "Test", a void return type, and have no parameters) and search for all test methods that match.
In my early career, I wrote several unit testing frameworks, and they tended to end up with a similar design. Thousands more had the same experience, and that commonality of experience is captured in the xUnit design pattern for unit testing frameworks.
The original implementation of this pattern was done in Smalltalk ("SUnit") by Kent Beck, and many more have followed in pretty much every programming language you can think of.
In the years since, some useful advanced features have been added, which we'll explore later in the workshop. But, under the hood, they're all pretty much along these lines.
Posted 8 months, 4 days ago on January 15, 2018