Unit testing is a coding practice dedicated to verifying the behavior of a unit of code against its specifications. Effectively, a unit test is nothing other than a piece of code that tests a code (usually a function, method or less frequently, a module), which informs us if they meet their design and behave as intended in different scenarios.
Unit tests are only one of the different types of software tests that should make up a complete testing pipeline. Others include integration tests, functional tests and performance tests.
Unit tests should be written during the development phase, to verify and document the correct behavior of a function or method.
Many software engineers run their unit tests locally during the development process, but most importantly, unit tests should be run in the CI pipeline to verify that no regressions are introduced in the code.
Studies from Microsoft Research and IBM show the efficiency of early stage testing on the robustness and reliability of softwares.
Unit tests are an early stage phase of testing that positively impacts the robustness and reliability of software: developers who don’t unit test usually face a significantly higher volume of bugs and software failures. Even though spending more time and resources on unit testing feels like a direct negative impact on the capacity to lead the project to its goal under time and resource constraints, it usually aids in the successful execution of the project. Here are 4 specific dimensions which benefit from unit tests that are recognised by software developers:
The ripple effect of unit testing described above impacts the success of a project by reducing unexpected errors and resources dedicated to fixing as well as increasing the success of the software amongst end users.
Like the name says, unit tests should test the smallest testable units in your code. Most often, these units correspond to a function or a method.
A best practice for unit testing is similar to an exhaustive bug report. You should find key elements of comprehension inside it: what is the unit being tested, what is the expected behavior, what is the actual behavior and how to reproduce the difference between the two.
In order to write a good unit test you should always keep a focus on simplicity. A unit test should be easy to code, easy to read, stable and as a consequence it should execute very quickly. Since you are testing a single unit your unit test should not access databases, file systems or other similar external factors.
Find out more about unit testing best practices in our dedicated blog post: An opening guide to understanding unit testing best practices
Unit tests are written from a software developer’s perspective, and therefore are code-aware: they are very much dependent on the code and data structures that they are testing, because their purpose is to verify the correct behavior of a piece of code.
On the contrary, functional tests are written from the user’s perspective. As such, they do not depend on the details of the code, as their goal is to verify the correct behavior of an end-to-end feature or functionality. As such, functional tests do not attempt to isolate units of code, but instead must take into consideration dependencies with web services or databases.
Code coverage is an indicator of the amount of code that is executed when a unit test suite runs. It can be useful to identify which portions of code have not been tested, or haven’t been tested well enough.
Code coverage can be measured based on different criteria: function coverage, statement coverage, path coverage, branch or decision coverage and condition coverage. Test runners usually collect coverage information according to several of the methods above.
It is often nearly impossible to achieve 100% test coverage, therefore, most software companies usually aim at 80%.
Mocking is a practice used to isolate units of code that have dependencies on other functions or objects, with several possible objectives:
The term mocking, however, is rather generic - as it refers to a range of techniques which help achieve the goal of code isolation. The most common amongst these are stubs and spies. Stubs replace the function call and its return with a simplified one defined by the developer, while spies allow to make assertions on how or when a function was called.
A classic situation where mocking comes to help is when a function involves some interactions with a database: it would be impractical and outside the scope of a unit test to verify that the data retrieved from the database is correct. For this reason, the call is usually stubbed and/or spied on.
We have a whole blog article just about mocking, you can read it here: What is mocking?
Unit tests are useless.
Many software engineers dismiss the importance of unit testing because they don’t appreciate the large impact they can have on the health of their software, and because their time consuming nature is a natural deterrent. The truth is that, no matter the size or maturity of a project, unit tests are one of the easiest ways to guarantee robust, professional code.
Unit tests are time consuming.
Software engineers can spend up to 35% of their workweek on testing. But tools to automate and speed up the process are now available on the market. Give Ponicode a go for free.
Unit tests are difficult to create.
While it’s true that the syntax of test frameworks can be hard to grasp, there are tools out there which simplify and greatly ease the process of writing exhaustive unit tests. Again, you can try Ponicode for free and see for yourself how simple it is to implement.
And there is more! Here are the questions we answered in our previous blog posts
What is Test Driven Development? (and how can you follow TDD in the Ponicode environment?)
What is the best testing framework for me? Where we introduce the pros and cons of Jest and Mocha