Wednesday, February 24, 2016

"The Art of Unit Testing", second edition - Book Review

In this post I will share my thoughts on a book I read recently. It is called "The Art of Unit Testing with examples in C#", second edition written by Roy Osherove.
Relatively old, written in December, 2013 in aroung 300 pages, Roy manages to cover all aspects of Unit Testing. How to start, how to structure your tests, utilize testing frameworks like NSubstitute, use test runners like NUnit to automate the process of running the tests.
How to refactor your code to handle better with tests, a short introduction to Test Driven Development, what types of testing frameworks exist(constrained/unconstrained), techniques how to create fake objects (stubs/mocks) in order to simulate testing environment.
Of course he uses .NET/C# related tools but roughly all principles apply in the same way to other programming languages and testing frameworks.

Unit testing is part of Test Driven Development and it a very useful technique to make sure your code works as expected. You first write the test and make sure it fails when it should fail. Then write the code that does the application functionality and make sure the test passes when it should pass. Run all the available unit tests and if all pass continue with writing test for the next application feature. If some of them fail refactor or debug the application code you just wrote. The good thing about test driven development is that it enables you to understand what are you trying to do, twice. That means, you think of the new feature that the application has to have, write a test about a certain functionality and make sure the test fails when it should fail. And then again think of the application feature and write the code that makes the test pass when it should pass.

Test Driven Development lifecycle
Test Driven Development lifecycle
So what is a Unit Test? Some people think a unit test must test a single function/method inside a system. Well according to Roy Unit test is a test that tests a single unit of work. That means if you have a rather complex function that calculates just "return of investment" you would create a test that tests that unit of work i.e return of investment, though you could separate the function in several different functions.

The book is separated in 4 parts plus an appendix with short explanation of the tools mentioned in the book.
What is new in the book? The author says that RhinoMocks is dead and that you should avoid it. It uses old testing technique and you should use frameworks that use Arrange-Act-Assert rule. Those would be NSubstitute, Typemock, FakeItEasy and many more.
The book is written for people who code and it teaches best practices for unit testing. If you have never written a unit test you should read it from start to finish.

Properties of a good unit test
Properties of a good unit test
Unit test definition
Unit test definition

Chapters:
  1. The basics of Unit Testing. This chapter covers what is a unit test, difference between unit and integration testing, a simple unit test example with NUnit and C# and a short introduction to Test Driven Development. SUT(System Under Test) or CUT(Class Under Test). Manual tests are stupid and not reliable, therefore automate the process of testing with unit tests.
  2. A first unit test. Exploring unit test frameworks, writing your first test with NUnit, NUnit attributes. This chapter cover several useful testing attributes like TestFixture, Test, TestCase, Ignore, ExpectedException, SetUp, TeadDown, Category etc... Roy says that he does not use SetUp and TearDown anymore because they just impair readablity of the test file and probably always you can develop helper functions that initialize object instead of doing that before and after each test.  
  3. Using stubs to break dependencies. This chapter defines what is stub, how to refactor code to make it testable with a stub, overcomming encapsulation problems and best practices to use stubs with your code. So what is a stub? A stub is a piece of code that fakes a certain behavior and enables you to test your code against that fake object. If you were to use real objects to test certain functionality that would be an integration test. 
  4. Interaction testing using mock objects. Defining interaction testing, what are mock objects, difference between fakes, mocks and stubs, using mocks best practices. So what is a mock object? A mock object is a fake object that is used to simulate certain behavior and all the communication between this fake object and the class under test is recorded in the mock. The test passes if the communication went well.
  5. Isolation (mocking) frameworks. Understanding isolation frameworks, using NSubstitute to create stubs and mocks, avoiding common missuses of isolation frameworks. This chapter also covers Arrange-Act-Assert rule and advanced ways to to handle stubs and mock in unison
  6. Digging deeper into isolation frameworks. Constrained vs Unconstrained frameworks, how unconstrained profiler-based frameworks work, defining values of a good isolation frameworks. Roy divides testing frameworks into constrained and unconstrained. Constrained frameworks are those that can test only the code written in the same programming language as the frameworks i.e only the public space. Constrained frameworks cannot test private, sealed, static methods. You are bound to the same rules as the compiler of your application. Unconstratined methods can test everything including, static, private, sealed members of a class. They can do this because they run before the compilation of your program using lower level APIs. In .NET all unconstrained frameworks are all profiler based which means, they use unmanaged profiling APIs wrapped around the running instance of CLR(Common Language Runtime). Some famous frameworks are, Java: PowerMock, JMocikt, C#: Typemock Isolator, JustMock, C++: Isolator++, Hippo Mocks.
  7. Test hierachies and organisation. Running unit tests during automated nightly builds, using continuous integration for automated builds, organising tests in a solution, test class inheritance patterns. This chapter cover some important asspects of software development. A development team should always have CI script that starts build automation script and runs all the tests available before building the software. I would recommend Jenkins for CI and Ant/Maven for build automation. As with any other things in your project unit tests should also be part of your source code repository. Make sure you maintain them along developing the software. Each class should have one test class and all tests should be related to the class under test. You can also use class inheritance in your tests if you think you repeat yourself.  Also interfaces or abstract classes to confirm certain functionalty is in the test class. 
  8. The pillars of good unit tests. Writing trustworthy, maintainable and readable tests. So unit tests should test only one single unit of work. Avoid using logic inside tests. Tests should be isolated from each other i.e don't run test inside other test. Unit tests are not integration tests and should not be treated as such. If you want to know the most important properties of a unit test this is the chapter to read.
  9. Integrating unit testing into organization. Becomming an agent of change, implementing a change using bottom-up or top-down approcah. If you are an agent of change expect to get tough questions and prepare to answer them. In each company you will find followers and blockers of the change. Top down is from the boss down to employees and bottom-up is vice versa. People always accept changes hardly so instead of pushing your idea forcefully try to implement some new working process that will lead to this change i.e implementing unit testing in the company. Also always use code coverage tools to make sure your tests cover all the necessary functionality in the software.
  10. Working with legacy code. Common problems with legacy code, where to begin writing tests, helpful tool for working with legacy code. Roy recommends this book on working with legacy code. So legacy code is not a joke and implementing unit tests in old code is a serious bussines. You should begin writing tests according to some priority, which features of the system have biggest priority. Of course you can also subdivide this very important software feature into code parts divided by complexity and dependency level. There are two strategies, easy-first and hard-first. Easy-first strategy is good if you have a team of unskilled unit testers which will enable them to become more skilled in time. With easy-first you expect new features to get tested harder until the hardest features are covered with tests. Hard-first is more suitable if you have a team of skilled unit testers, and at first it takes lot of time to test new features but after the hardest parts are being tested the time to test new  feature drastically reduces. Some known tools are: JustMock, Typemock Isolator, JMockit, Vise, FitNesse, NDepend, Resharper, Simian and TeamCity
  11. Design and testabiltiy. Benefits from testability design goals, pros and cons on design for testablity, tackling hard-to-test design. Make methods virtual, use interfaces as superclasses, make classes non-sealed, avoid instantiating concrete classes inside methods with logic, avoid direct calls to static methods, prefer calls to instance methods that later call statics, avoid constructors and static constructors that do logic, separate singleton logic from singleton holders. How do you test sensitive IP or pieces of software you are not allowed to reverse engineer? Sometimes programming languages or application frameworks are designed for testabiltiy so no additional testing is required apart from the application logic. 
  12. Appendix, Tools and frameworks. All tools and frameworks mentioned in the book are covered here with a short explanation. There are isolation frameworks, test frameworks, test APIs, IoC containers, database testing, web testing, UI testing(desktop), thread-related testing, acceptance testing, BDD-style API frameworks.
Well since this book is very comprehensive and cover anything you might think of when writing your tests I give it 4.8/5. I would give 5/5 but first I want to put my new knowledge into use and see if it is useful.

I upload some useful screenshots on my tumblr blog: http://tunephp.tumblr.com/

Thanks for reading my review. Best regards, Vlado.