Why do we Unit Test (and what’s the purpose of Code Coverage)?

What is Unit Testing? Unit Testing is the approach of testing the smallest parts of our code in isolation from other code. In Java these are methods in our classes. In Javascript these are our functions.

There are many unit testing frameworks that help us with our unit tests. In Java we have JUnit and TestNG, both have been around for a long time. The key part they help us with is making ‘assertions’ about the results of our code under test, to check the results and confirm when we call our code, it returns the expected results.

Why do we Unit Test? As developers we want to know our code works, that it does what it is supposed to do. We use unit tests to give us confidence about our code, so we know it works.

We can use unit tests to help with code changes or refactoring efforts to improve existing code. If existing tests are available, you can use them to check that they are showing you were the issue is that you need to fix (assuming there’s a test for that part of the code), or check that the existing code is working before your refactoring changes.

A typical flow for using unit tests to help with a defect fix looks like:

Run test, confirm test fails. Make code fix, confirm test passes. If the unit test for the related code doesn’t have an existing test for the code where the issue is, add a new test. Write a test for the expected result which should initially fail, rerun after the code fix and now the test should pass to confirm your fix is working.

Run other related tests in the same area to check there are no unexpected impacts to other existing code.

We can also use unit tests as part of a CI/CD pipeline – when our tests pass, the code is ready to include in a new build, and depending on your development process, ready for additional tests, integration testing, UAT testing and promotion into production.

Many industry studies have shown that it’s always cheaper to find and fix defects in our code earlier than later. The time invested to develop unit tests saves us in the long run if we can find issues earlier and avoid finding issues later, e.g. in production.

Any number of unit tests does not prove that our code is 100% correct however, neither does it prove the absence of bugs. If our unit tests are effective though, they can give us confidence that our code is working as expected for a range of tested inputs.

What is an effective test?

An effective unit test must have assertions to check and confirm the code when executed with given parameters returns the expected results. In JUnit, the framework provides a number of assert statements, like assertTrue(), assertEquals() that you use to confirm the expected results.

A unit test without assertions that only calls the code under test is not an effective test. Without assertions, the test is useless if you’re not checking the expected results.

Similarly for assertNotNull(). If the only thing your test does is check that there’s a non-null result, this doesn’t confirm that the code is returning the expected result, it only confirms a value is returned. This is not particularly useful when we expect a unit test to confirm that the code is returning the expected results (i.e. it is working).

For any given method that takes parameters, you should have a range of test methods to confirm the expected results for that range of input values. You should obviously test with expected/valid input values, but you should also test with a few unexpected/invalid values: high values, low values, empty, short and long strings. In all cases you should assert and confirm that the method returns the expected results.

How does Code Coverage relate to Unit Testing?

Code Coverage tells you how much of your code was execute by your tests. As a tool it is useful during the development of your unit tests, because it helps highlight where your tests may have missed paths through the code. For example, if the code you are testing has an if..then..else condition and your test calls the code with parameters that causes the if condition block to be tested but not the else block, Code Coverage helps highlight untested code.

The problem with Code Coverage though, is that by itself, Code Coverage is not an indication of effective tests or even working tests. If you have a test without an assertion that calls a method with a single block of lines of code and each of those lines executes, Code Coverage will report 100% coverage on this block. Without an assertion, this test doesn’t test that the code works, and therefore is worthless. It does have 100% Code Coverage though. This is why relying on Code Coverage alone is not useful, or sensible, because it doesn’t tell you how effective your tests are. It only tells you how much of the code was executed. Code that is executed by a test but is not confirmed if it is returning expected results or not with assertions, is just executed code, not working code.

Remember your assertions.

Use Code Coverage as a tool to help you find untested code, not as an ultimate goal: writing tests to achieve Code Coverage is not useful. Developing effective tests is your goal.

What defines ‘simple’ code?

Simple code is not complex code. Ok, well what does complex code looks like? We don’t like or want complex code because it is:

  • hard to read
  • hard to understand
  • difficult to update and maintain

Ok, so simple code therefore is:

  • easy to read
  • easy to understand
  • easy to update and maintain

As a result of the above, other benefits become more easily within reach. For example, simple code is easier to unit test.

What can we do to ensure we write simple code? Many of the commonly known (but not commonly applied) industry best practices lead to simpler code. For example:

  • Single Responsibility Principal: Bob Martin defines this as a ‘single reason to change‘. A class should have a single responsibility, one feature that it is responsible for. If there is more than one reason that would require changes to this class, it has more than one responsibility, and therefore is doing ‘too much’.
  • A class or a method should do one thing and do that one thing well. Not 10 things, not 5, just one thing. Limiting to just one thing reduces the opportunity for complexity to creep in (this is really the same idea as the Single Responsibility Principal).
  • A single method short be short enough that you can easily read it and grasp the whole intent. Too long is when you have to scroll page after page, and at that point, it’s difficult to grasp the entire purpose of the method, without scrolling around and re-reading. If it takes you too long to read to the end of a method, and by the time you get there you’ve already forgotten what it was doing at the start, and the start of the method is already several pages off the top of your screen, your method is too long.
  • Clearly named variables, methods and class names: a clear name that describes what the variable is for (it’s purpose, what does it represent), what a method does, what a class does, helps to convey it’s purpose and improve understanding. A method that does something else other that what it’s name describes it not simple and it not easy to understand. We don’t like surprises.
  • Clear documentation. In Java we use JavaDoc. Your JavaDoc should describe what the Class does, what each public (at least) method does. It should NOT state the obvious, it shouldn’t repeat what is already implied from your clear class and method names. If you’re just repeating what the method name says, you’re not aiding readability, you’re adding more content that I have to read, but for no gain. For example, this is not useful documentation, although many developers do this:
/**
* This method creates a new account.
*/
public Account createAccount(){
...
}

… I know it’s a method because I am a developer, you don’t need to tell me that. I know this method creates an account, because the method name says so. This JavaDoc adds no additional value, and if there’s no additional detail to be added, it would be best just left out.

If you set out from the start to create simple code, it’s more easily achievable than creating something too complex and then trying to simplify. Refactoring is your friend, and you should always invest time to refactor when you’ve finished your first iteration of getting your code working. However, by aiming to avoid complexity from the start you can make your job easier in the long run.

My software development blog posts from 2018: AWS, Twitter bots, Machine Learning, Docker, Kubernetes and more!

Looking back, I’ve investigated and played with a lot of interesting stuff this year, I was pretty busy 🙂 Early in the year I was doing a lot of experimenting with AWS as part of my prep for the AWS Solution Architect Associate exam, so a lot of my posts this year were AWS related, but I did some a some time working on some other projects too.

Here’s a look back at some of my favorite personal projects and blog posts during the year:

  • Using AWS Sagemaker to train Machine Learning models – here and here
  • mvmdvm and Pi-Star setup – multi digital voice Amateur Radio modes on a Raspberry Pi – here and here

Phew that’s quite a lot! I sincerely appreciate the feedback and comments I receive on my posts and hearing that at least some of my content is useful to others. I look forward to continuing to share my thoughts and write more content that hopefully will be continue to be useful in the coming year.

Happy New Year!

InfoQ interview with Martin Fowler : 2nd Edition of his 1999 Classic, Refactoring, is now shipping!

Martin Fowler’s classic software development book, Refactoring, was first released in 1999. It’s been a staple on my bookshelf since I got a copy in 2000, something that I regularly refer back to for advice on how to improve the structure and maintainability of my code.

The 2nd edition of the book has now been released, updated for 2018, with all the code examples in the book which were previously in Java now replaced with equivalent examples in JavaScript. I have my copy on order with Amazon and should be receiving my copy before the end of the year.

InfoQ have a great podcast interview with Martin, discussing the motivations for releasing an updated edition for 2018, 19 years after the 1st edition was released. Check out the interview here.