Part 1. Getting started
This part of the book covers the basics of unit testing.
In chapter 1, Iâll define what a unit is and what âgoodâ unit testing means, and Iâll compare unit testing with integration testing. Then weâll look at test-driven development and its role in relation to unit testing.
Youâll take a stab at writing your first unit test using NUnit in chapter 2. Youâll get to know NUnitâs basic API, how to assert things, and how to run the test in the NUnit test runner.
Chapter 1. The basics of unit testing
This chapter covers
- Defining a unit test
- Contrasting unit testing with integration testing
- Exploring a simple unit testing example
- Understanding test-driven development
Thereâs always a first step: the first time you wrote a program, the first time you failed a project, and the first time you succeeded in what you were trying to accomplish. You never forget your first time, and I hope you wonât forget your first tests. You may have already written a few tests, and you may even remember them as being bad, awkward, slow, or unmaintainable. (Most people do.) On a more upbeat note, you may have had a great first experience with unit tests, and youâre reading this to see what more you might be missing.
This chapter will first analyze the âclassicâ definition of a unit test and compare it to the concept of integration testing. This distinction is confusing to many. Then weâll look at the pros and cons of unit testing versus integration testing and develop a better definition of a âgoodâ unit test. Weâll finish with a look at test-driven development, because itâs often associated with unit testing. Throughout the chapter, Iâll also touch on concepts that are explained more thoroughly elsewhere in the book.
Letâs begin by defining what a unit test should be.
1.1. Defining unit testing, step by step
Unit testing isnât a new concept in software development. Itâs been floating around since the early days of the Smalltalk programming language in the 1970s, and it proves itself time and time again as one of the best ways a developer can improve code quality while gaining a deeper understanding of the functional requirements of a class or method.
Kent Beck introduced the concept of unit testing in Smalltalk, and it has carried on into many other programming languages, making unit testing an extremely useful practice in software programming. Before I go any further, I need to define unit testing better. Hereâs the classic definition, from Wikipedia. Itâll be slowly evolving throughout this chapter, with the final definition appearing in section 1.4.
Definition 1.0
A unit test is a piece of a code (usually a method) that invokes another piece of code and checks the correctness of some assumptions afterward. If the assumptions turn out to be wrong, the unit test has failed. A unit is a method or function.
The thing youâll write tests for is called the system under test (SUT).
Definition
SUT stands for system under test, and some people like to use CUT (class under test or code under test). When you test something, you refer to the thing youâre testing as the SUT.
I used to feel (Yes, feel. There is no science in this book. Just art.) this definition of a unit test was technically correct, but over the past couple of years, my idea of what a unit is has changed. To me, a unit stands for âunit of workâ or a âuse caseâ inside the system.
Definition A unit of work is the sum of actions that take place between the invocation of a public method in the system and a single noticeable end result by a test of that system. A noticeable end result can be observed without looking at the internal state of the system and only through its public APIs and behavior. An end result is any of the following:
- The invoked public method returns a value (a function thatâs not void).
- Thereâs a noticeable change to the state or behavior of the system before and after invocation that can be determined without interrogating private state. (Examples: the system can log in a previously nonexistent user, or the systemâs properties change if the system is a state machine.)
- Thereâs a callout to a third-party system over which the test has no control, and that third-party system doesnât return any value, or any return value from that system is ignored. (Example: calling a third-party logging system that was not written by you and you donât have the source to.)
This idea of a unit of work means, to me, that a unit can span as little as a single method and up to multiple classes and functions to achieve its purpose.
You might feel that youâd like to minimize the size of a unit of work being tested. I used to feel that way. But I donât anymore. I believe if you can create a unit of work thatâs larger, and where its end result is more noticeable to an end user of the API, youâre creating tests that are more maintainable. If you try to minimize the size of a unit of work, you end up faking things down the line that arenât really end results to the user of a public API but instead are just train stops on the way to the main station. I explain more on this in the topic of overspecification later in this book (mostly in chapter 8).
Updated Definition 1.1
A unit test is a piece of code that invokes a unit of work and checks one specific end result of that unit of work. If the assumptions on the end result turn out to be wrong, the unit test has failed. A unit testâs scope can span as little as a method or as much as multiple classes.
No matter what programming language youâre using, one of the most difficult aspects of defining a unit test is defining whatâs meant by a âgoodâ one.
1.1.1. The importance of writing good unit tests
Being able to understand what a unit of work is isnât enough.
Most people who try to unit test their code either give up at some point or donât actually perform unit tests. Instead, either they rely on system and integration tests to be performed much later in the product lifecycle or they resort to manually testing the code via custom test applications or by using the end product theyâre developing to invoke their code.
Thereâs no point in writing a bad unit test, unless youâre learning how to write a good one and these are your first steps in this field. If youâre going to write a unit test badly without realizing it, you may as well not write it at all and save yourself the trouble it will cause down the road with maintainability and time schedules. By defining what a good unit test is, you can make sure you donât start off with the wrong notion of what your objective is.
To understand what a good unit test is, you need to look at what developers do when theyâre testing something.
How do you make sure that the code works today?
1.1.2. Weâve all written unit tests (sort of)
You may be surprised to learn this, but youâve already implemented some types of unit testing on your own. Have you ever met a developer who has not tested their code before handing it over? Well, neither have I.
You might have used a console application that called the various methods of a class or component, or perhaps some specially created WinForms or Web Forms UI that checked the functionality of that class or component, or maybe even manual tests run by performing various actions within the real applicationâs UI. The end result is that youâve made certain, to a degree, that the code works well enough to pass it on to someone else.
Figure 1.1 shows how most developers test their code. The UI may change, but the pattern is usually the same: using a manual external tool to check something repeatedly or running the application in full and checking its behavior manually.
Figure 1.1. In classic testing, developers use a graphical user interface (GUI) to trigger an action on the class they want to test. Then they check the results.
These tests may have been useful, and they may come close to the classic definition of a unit test, but theyâre far from how Iâll define a good unit test in this book. That brings us to the first and most important question a developer has to face when defining the qualities of a good u...