Table of Contents
In web development, Unit Testing plays a key role in guaranteeing the quality and reliability of your applications. In this article, you’ll learn what they are, why you should implement them and, most importantly, how to do it.
What is Unit Testing?
Unit testing is a testing practice in which sections of code, called “units”, are tested individually to check that they function correctly. These units can be functions, methods or even classes. The main purpose of Unit Testing is to ensure that each unit works as expected, independently of other parts of the application. Unit tests must be automated, reproducible and quick to execute.
An example of a unit test
Let’s take the example of a simple function that calculates the sum of two numbers. Here’s how a unit test for this function could be written using the JavaScript testing framework, Jest :
// Function to testfunction addition(a, b) {return a + b;}// Unit testtest('Test the function of addition', () => {// Setup step (installation)const a = 5;const b = 10;// Action step (exection of the function to test)const resultat = addition(a, b);// Assert step (Check for result)expect(resultat).toBe(15);});
In this example, we create a unit test that checks whether the addition function returns the expected result for the given values. The arrangement step consists in setting up the input values required for the test. The action step executes the function to be tested with the specified input values. Finally, the assertion step checks whether the result obtained corresponds to the expected result.
Why is Unit Testing important?
To answer this question, we need to ask another. What happens if there is no Unit Testing in place?
Here, I have an anecdote to tell you. I remember that, during my first professional experience, no unit tests were written for a week because of a heavy workload. Fatally, this led after several months to an astronomical number of bugs and regressions following the implementation of a seemingly innocuous feature.
The consequences were significant, with bugs discovered ten days after the release, and a whole week devoted to resolving them. Had we used Unit Testing, this situation could never have arisen. We would have spotted the bugs before they went live, which would have saved us a week’s development time. The cost of Unit Testing is much lower than the cost of the problems that occur in its absence.
The benefits of Unit Testing
- Trust in the code: Unit Testing provides a layer of security by checking that each unit of code works correctly. They help to reinforce the trust of developers, test teams and users in the quality of the code and the reliability of the application.
- Verify functionality: Unit tests ensure that each unit of code functions correctly, in accordance with its specifications. They help validate the unit’s expected behavior and detect logic or calculation errors.
- Code maintainability: By writing unit tests, developers also create a solid documentation of their code. These tests serve as a reference for understanding the expected behavior of each unit of code, facilitating future maintenance and code modifications.
- Error detection: unit tests are run frequently, ideally every time the code is modified. This enables errors and bugs to be detected quickly, making it easier to resolve them before they spread to other parts of the application.
- Reusability: Unit testing encourages the design of modular, well-structured code. By isolating each unit of code, unit tests promote reusability, as units can be tested independently and easily integrated into other parts of the application.
- Refactorings: Unit tests enable refactorings to be carried out with confidence, as they guarantee that essential functionality is preserved even after major code modifications. They also facilitate software upgrades by enabling new functionalities to be validated quickly, with no negative impact on existing ones.
Reinforcing product security
Another major benefit of unit testing is to enhance application security. By writing unit tests, developers can check that implemented functionalities do not present security risks such as SQL injections, Cross-Site Scripting (XSS) attacks or authentication and authorization problems. Unit tests validate the expected security behavior of code units, and ensure that appropriate security measures are in place.
In addition, unit tests can also help identify potential vulnerabilities linked to logic or data processing errors. For example, by testing user inputs with different values and conditions, unit tests can detect validation errors that could enable attacks or malicious data manipulation.
How to make effective unit tests
It’s not enough to set up unit tests, you also have to do it properly. Here are some of the essential characteristics of a successful unit test.
1. Automation
Unit tests need to be automated, which means they are written using specific frameworks or tools to execute the tests programmatically. Automating unit tests means that they can be run regularly and consistently, without relying on manual intervention.
2. Unit
Unit Testing focuses on a specific unit of code, such as a function, method or class. It aims to test this unit independently of other parts of the application. By focusing on specific units, unit tests make it possible to verify the correct operation of each component of the code in isolation.
3. Isolation
A unit test must be isolated, i.e. it must not depend on other parts of the system. All external dependencies must be simulated or replaced by false objects (mocks) to ensure that the test focuses solely on the unit under test. This isolation enables accurate, independent evaluation of the unit under test.
4. Speed
Unit tests must be quick to execute. They are designed to be run frequently, ideally every time the code is modified. Rapid testing enables errors to be detected quickly and facilitates the iterative development process. By identifying problems quickly, developers can solve them more efficiently.
5. White box
Unit Testing follows a white-box approach, which means it has inside knowledge of the code it is testing. It can examine code structure, execution paths and logical conditions to design effective test cases. This approach enables complete, targeted coverage of different parts of the code.
6. Reproducibility
Unit tests must be reproducible, which means they must deliver the same results every time they are run. This enables problems to be detected consistently and makes it easier to debug errors. By having consistent results, developers can better understand problems and solve them more efficiently.
By meeting these criteria, Unit Testing becomes an essential part of a robust, high-quality software development process. They enable rapid error detection, facilitate code maintenance and ensure application stability.
Example: unit tests as we implement them
That’s the theory. Now for a practical application: Unit Testing as we do it at Yogosha. We attach great importance to their use, at both front-end and back-end levels.
For front-end testing, we use Jest. Our Jest tests focus mainly on “utility” elements reused in several places in the application, such as our field validation regular expressions. However, we don’t perform tests for the logic of front-end components, as our approach prioritizes logic at the back-end level.
On the back-end, we use phpspec, a PHP-specific testing framework. Phpspec tests are everywhere and are essential, since this is where all business logic is defined. Every class containing logic is carefully tested to ensure that it functions correctly. We avoid creating bulky classes with high complexity, preferring to split our logic into several classes in line with the DDD (Domain-Driven Design) model. This approach allows us to separate our logic into distinct domains, making our tests and code clearer, easier to understand and maintain.
E-Book: Bug bounty, the ultimate guide to a successful program
Learn how to build your Bug Bounty program, make it attractive and leverage hackers to identify high-risk vulnerabilities.
GET THE E-BOOK!
Difficulties related to Unit Testing
Implementing unit tests can present certain difficulties, which can be overcome by using appropriate methods.
Dependence on external resources
A common difficulty in implementing unit tests is dependence on external resources, such as databases, web services or files. When unit tests depend on these resources, they become slower, less reliable and more difficult to isolate.
To solve this problem, we recommend using techniques such as substituting these resources with duplicates (mocks or stubs) that simulate their behavior. This makes it possible to test code units independently, without worrying about external dependencies.
Dealing with the growing complexity of applications
Another challenge is the increasing complexity of applications. The larger and more complex an application, the more difficult it becomes to write exhaustive unit tests and maintain their coverage at a satisfactory level. In such cases, it makes sense to follow design principles such as modularity, cohesion and decoupling. A well thought-out design facilitates the creation of unit tests that are more focused and easier to write. It is also important to prioritize unit tests by focusing on critical parts of the code and identifying the most relevant test scenarios.
Time-consuming testing
Another major challenge is the time required to write and execute unit tests. In an agile development environment, where iterations are frequent, it is essential that unit tests are fast and can be run frequently.
To speed up test execution, techniques such as parallel testing, the use of reduced data sets and the caching of slow or expensive resources are recommended. In addition, it is important to optimize unit tests by avoiding excessive dependencies or slow operations that could slow down their execution.
Maintenance
Finally, a common challenge is the maintenance of unit tests. As code evolves, unit tests need to be updated to reflect the changes made. This can become tedious, particularly when there are a large number of unit tests.
To facilitate maintenance, we recommend adopting practices such as writing legible and understandable unit tests, using descriptive test names and reducing dependencies between tests. Automating test execution and integrating unit tests into a continuous integration pipeline can also simplify maintenance and enable early detection of problems introduced by code changes.
To sum up
Implementing unit testing presents a few challenges, but nothing that can’t be overcome by following a few good practices. By adopting measures such as dependency isolation, modular design, performance optimization and regular maintenance, Unit Testing becomes a powerful tool for ensuring the quality, reliability and maintainability of software applications.
Read also: What is Offensive Security? An introductory guide to OffSec