Author: Roy Osherove
Audience: C# users and others using statically typed languages
Reviewer: Alex Armstrong
If you don't already use unit testing, this book is a complete and clear guide to the idea and how to implement it.
This is the second edition of Roy Osherove's book on Unit Testing and its important to take account of the word art in the title.
There is no one way to go about testing that is set in stone and in fact this second edition includes a significant change from the first version in that it no longer advocates RhinoMocks. In the preface Osherove says of the framework that figured laregly in the previous edition:
“Stay away from it. It is dead. At least for now."
There is also a new chapter that goes deeper into isolation frameworks. But if, like me, you have not read the original you'll come to it in due course.
The book is intended for “anyone who writes code and is interested in learning best practices for doing so. While its content applies to any object-oriented, statically typed language the one specifically addressed is C# with NUnit as the test framework. Code samples are written in C# and code is supplied on the book's website to enable you to actively follow through its examples.
There are four parts to the book and if you are new to unit testing you should start at the beginning.
Part 1: The Basics has two chapters. In the first, Osherove first looks at definition of unit testing, distinguishes it from integration testing and put it in context with test-driven development. First we meet a classic definition of a unit test:
a piece of code (a method or function) that invokes another piece of codes and checks the correctness of some assumptions
This is then refined in the light of the fact that Osherove considers a “unit” to mean a “unit of work” (or a use case) that can span as little as a single method up to multiple classes and functions. A unit test therefore has to have the same span. What remains the same is that if the assumptions on the end result turn out to be wrong the unit test has failed.
Next Osherove lists the properties of a good unit test starting with that it should be automated and repeatable and easy to implement and concluding with that it should be easy to determine how to pinpoint the problem when it fails.
Integration testing, according to Osherove differs by using one of more real dependencies of the units under test, whereas unit tests isolate the unit of work from its dependencies and run the risk of testing too many things at once.
After an example of a simple unit test we come to a discussion of test driven (or test first) development (TDD) which is shown is a flow chart to be an incremental process that employs small steps: write test, write code, refactor, write next test.
Chapter 2 introduces the free and open source .NET unit testing framework NUnit. Osherove guides you through the process of installing NUnit using NuGet – the free extension to Visual Studio that lets you search, download and install references to popular libraries. The chapter also introduces a software project that is used throughout the book to motivate and illustrate the process of unit testing the code for which is provided on the book's website. You are now ready to write your first unit test and encounter the Assert class in NUnit. Osherove explains the different ways to run the test, discusses adding additional positive tests and guides you through refactoring them to be parametrized and thus more easily maintainable. As the chapter proceeds the code is presented in short, digestible blocks using boldface and annotations to ensure you can easily follow the arguments about good practice that are being made in the chapter.
Part 2 is on the core testing and refactoring techniques you need for real world situations where you encounter dependencies on objects over which you have no control. Chapter 3 starts by looking at the way in which stubs can help break down dependencies, showing how code can be refactored to use stubs to make it more testable and introducing the idea of seams -places in your code where you can plug in different functionality such as stub classes. It also introduces the idea of testable object-oriented design, a topic more fully explored in the book's final chapter.
There is some confusion in this chapter in that it mentions fakes and mocks as well as stubs, deferring a more complete definition to the following chapter where it is clarified that while both stubs and mocks are fake objects it is only mocks that can fail tests because only mocks can assert something in your test. This has ramifications such as you can only have one mock in a test.
Chapter 5 looks at isolation frameworks. These are also known as mocking frameworks but Osherove feels that the term "mock" is too overloaded for this use as the frameworks, which create and configure fake objects at runtime, thus saving the developer from writing repetitive code to assert or simulate objects interactions, provide both stubs and mocks. The framework chosen for this purpose is NSubstitute (Nsub) for short, although Osherove admits he found the choice between Nsub and FakeItEasy. Nsub was selected for its superior documentation.
Later in the chapter he explains that he didn't choose Moq, which is the most used isolation framework among readers of his blog, partly because the term “mock” is overused and also on the grounds of “bad error messages” and although Rhino Mocks still came second in his poll he points out it is no longer being actively developed and is losing ground. More information on specific isolation frameworks, together with other tools discussed throughout the book is included in its appendix.
The chapter concludes with three advantages of using isolation frameworks over handcoded fakes:
- Easier parameter verification
- Easier verification of multiple call methods
- Easier fakes creation
He also looks at four traps to avoid when using them:
- Unreadable test code
- Verifying the wrong things
- Having more than one mock per test
- Overspecifying the tests
Chapter 6, with the title Digging deeper into isolation frameworks is new in this edition. It also goes wider in that it is informed by isolation frameworks for Java, C++ and other static languages and divides the frameworks into two groups, constrained, because they are limited in what they can fake; and unconstrained which don't generate and compile code at runtime that inherits from other code, Typemock Isolator seems to be the one that attracts most attention although Moles (a.k.a MS Fakes) and JustMock are other .NET examples mentioned.
Part 3 covers techniques for managing and organizing unit tests. The first of its two chapters looks at the role of unit testing as part of an automated build process, looking in more details at continuous integrations (CI), pointing out that tests must be part of source control and looking at patterns for mapping test to projects, to classes and to specific units of work
Chapter 8 discusses three basic pillars of good unit tests – readability, maintainability and trustworthiness. It emphasizes the need for test isolation and examines four antipatterns that can indicate broken test isolation:
- Constrained test order
- Hidden test call
- Shared-state corruption
- External shared-state corruption
Part 4: Design and process rounds out the book with discussion of some common real world issues. It starts in Chapter 9 by looking at the problems of integrating testing to an existing organization - and here it is obvious that Osherove has a lot of experience and has tips to pass on and Chapter 10 focuses on problems with legacy code.
The final chapter, Chapter 11 raises the questions why and how should you design for testability? Osherove says that he has no easy answers but the questions are interesting.
Overall this is a well balanced book. It is highly readable and also gives enough code to be a practical handbook. While the code is in C# readers who use Java or C++, which have similar unit testing frameworks, will find the patterns and antipatterns presented readily transferable.
Recommended to developers who don't already employ unit testing or who haven't yet mastered it.