Before we get into unit testing we should ask ourselves why would anyone write code to test other code? Is there any reason to do that extra work? It turns out that there are many reasons for that. Firstly, you’re going to make sure that given function works as expected. Secondly, if you decide to change the function, you still have it tested, so in case of some incorrect change you’ll know immediately that something is wrong. Thirdly, your code will have better design, because (once you figure it out) you’ll most likely avoid all dependencies and you’ll make sure that your code is as easy to use as possible.
What is unit testing?
You take smallest possible piece of code (a unit) and test it. Commonly the smallest possible unit is function. Each test should be indepenedent of others and should only test only one thing.
You should run tests periodically, in some cases it’s possible to make your environment run your tests after each code change. I also like to double check my code by running tests before I push them, so whenever someone downloads my code works as expected (at least in terms of tests ;P). If you use continous integration you should configure it to run all tests once a new commit comes in.
Most languages have some kind of unit testing framework. This article focuses on xUnit.Net.
Test Driven Development (TDD) is a way of writing software. It’s not the same as unit testing. TDD is how you “drive” the process. The principle is that you firstly write test, then you write a piece of code (method/function), then you run the test. Once you do that you write code to make the test pass.
We call this pattern of working RED-GREEN-REFACTOR. RED because you write test which fails firstly. It’s because you want to make sure that the test works correctly. It results in error obviously (which is usually marked by red color). GREEN stands for test which has passed, because at this stage we write minimum code to make the test pass (similary the passed test is usually marked by green color). The third stage is REFACTOR which means that we improve code quality. Once we’re satisfied with results we go on to create next tests. In so doing our development process is driven by tests.
This is the bare minimum about TDD. TDD can also include lots of different tests e.g. acceptance tests, integration tests. While you should familiarize yourself with TDD, it’s not the main point of this post. Here I’ll teach you how to create basic unit tests for your code.
Let’s start by creating new solution with two projects. First project will be “Class Library (.NET Core)” (I named it ECommerce) and second will be “xUnit Test Project (.NET Core)” (I named it ECommerce.Tests).
Then add your library project as a reference to your xUnit project (refer to screenshot below).
ShoppingCartTest. We’re ready to go!
Your task is to create a class which will handle shopping cart calculations. It holds a number of items. Each item has a net price (price without VAT), VAT (percent) and quantity. Cart calculates total net value, total VAT value and a total value to pay (net price + VAT). Whole class is listed below. I haven’t included calculations, those I’ve left for you to complete.
Your first test
Each test is a function inside
ShoppingCartTest class with a
Fact or a
Theory attribute. We’ll start with a
Fact because it’s the most basic.
Our first test will check if our class correctly calculates total net value of a shopping cart. We’ll start by creating shopping cart, adding item into it, calculating expected value and checking if shopping cart returned correct value.
Now, run the test by clicking “Run All” inside “Tests Explorer” window or by right clicking and choosing “Run tests” (you can also use following shortcut
OK, it failed. It’s your turn, go back and make
ShoppingCart.TotalNetValue return correct value.
Got it? Great, rerun the test to make sure it works correctly. Make another test to check wheter your changes work when quantity of an item is bigger than 1.
Your first theory
This time we’re going to check wheter or not our code correctly calculates VAT value. Let’s write another test. It’s somewhat similar to previous one, but instead of a plain sum we’re going to sum calculated VAT values.
Again run the test, fix
ShoppingCart.TotalNetValue and rerun the test.
Let’s stop for a moment and think about it. We’re only testing one item (by means of quantity). We want to make sure that then calculations are correct when someone adds 1 piece of item or more. This example may be a bit far fetched, but technically you’d want to check a couple of cases sometimes using same tests. For this you use a Theory, basically you can create a tests and supply with with different arguments to check different cases.
To do that we’re going to replace
Fact with a
Theory. Theory needs a set of data, we will supply the data via
InlineData attributes. Once we’re done with we’re going to edit our test method to receive data (via parameters). This requires only a couple of changes.
Arrange – Act – Assert pattern
Did you notice that above tests are created in similar fashion? We call it Arrange – Act – Assert (also known as AAA) which is a pattern for writing unit tests. It divides a test into 3 sections. Each one with separate set of responsibilities.
We use the first section “Arrange” to prepare (arrange) all data needed to run the tests.
Second section “Act” runs (act on) the the code under test.
We use the last section “Assert” verify (assert) the results.
The AAA helps separate setup, the thing being tested and the verification process.
Take a look at our previous test again to see how to works:
I’ve only introduced you to a couple of assertions. Let’s take a look at other available assertions for our unit testing needs.
NotNullto check wheter object is empty or not
NotEqualto check wheter objects are equal or not (hint: it also works for collections)
Falseto check result state
NotEmptyto check wheter enumerable is empty or not
DoesNotContainsto check wheter enumerable contains value (hint: it also works for strings)
Singleto check wheter enumerable contains only one element
Throwsto verify that a method throws exception
We should always pay attention to edge cases. Unit testing works nicely in this regard e.g. when you have a method which does some calculations you could write a regular test case, a test case which could check division by 0 and a case which check wheter or not a variable is beyond certain range (it is negative or user tries to withraw more that 100% of money from bank account).
There are still some cases which could (probably) break your code. There is still some more work to do. In similar manner, write a tests and then try to fix your code for following edge cases:
- it should throw
ArgumentOutOfRangeExceptionwhen vat is less than 0 or more than 100%
- ShoppingCart should throw
ArgumentOutOfRangeExceptionwhen product price is less than 0
- it should throw
InvalidOperationExceptionwhen you add an item with negative quantity
- ShoppingCart should limit maximum quantity of one item to 10 (we want to make sure that one user won’t take whole stock in one go), so if user tries to add more than 10 we trim it down to 10
Qualities of a good unit test
There are a few qualities which designates a good unit test. Let’s take a look at some basic traits of quality unit tests.
- Single unit test should test one unit of code (most likely a method), hence the name “unit test”. If you test more than one method then you have multiple points of (possible) failure, so you cannot be sure what exacly failed.
- Unit tests should be independent. By this I mean that it does not depend on environment, so other software developers can run it without additional setup.
- Good tests should not depend on other test. Each test is independent, so you know exacly what failed and what not. If the tests are interdependent then one failed test can cause all other tests to fail, in this case how would you know which test is broken?
- It should be easy to understand. By this I mean mostly that you should follow Arrange – Act – Assert pattern. You’ll have clear division of what is going on. This also means that your tests will be rather short. If your test is long or complicated then something most likely is wrong (consider the test and the thing being tested, maybe code design needs some thinking).
- Unit tests should be fast. Number of tests depends on the size of your code base and your will to create the tests. Potentialy you can create thousands of these. You don’t really want to make tests that run really long. Fast tests ensure faster feedback loop.
Practicing TDD and unit testing using code kata
If you want to get some practice with TDD and testing then you can do some code katas. Code katas are software development practice sessions. You take some problem and try to solve it.
This practice is useful when you want to learn to solve new problems, get acquainted with TDD or learn new programming language.
The best part of it is that code katas work for any language and you get to learn certain way of thinking and analysing problems.
If you want to read more about kata then head on to Kata – the only way to learn TDD.
As you can see basic unit testing is fairly simple. Some may argue that unit testing takes too much time. It takes some time, but we need to think of it in terms of return of investment. If you test something now then it will produce less errors in the future. I myself am not advocating 100% coverage (meaning that whole code is covered by tests). It depends on project, but in terms of common sense you could just test 20% of most important code or 20% of code which produces most bugs. While the opinions on this matter are many this is just an introduction, learn the basics and then try some more advanced stuff and different opproaches.