Scroll Top

Unit Testing done right: 10 best practices to make the most of it

UNIT TESTING

 Unit Testing done right10 best practices to make the most of it

Why should you test your code, and what are the best practices that ensure your code is bug-free.

In software development, coding is one side of the coin, and testing the code is the other. 

Testing software is essential to ensure the deployment of the highest quality software. Testing promotes healthy and reliable software development where quality is of paramount importance. And among the various levels of testing, unit testing is where the process commences.

In this blog, we explore what unit testing is, the objectives of unit testing, what are manual and automated unit testing, TDD, best practices that ensure full-fledged unit testing, and the limitations of unit testing. Furthermore, we have included some examples for your reference.

By the way, if you just want to skip to the best practices for unit testing, click here.

What is unit testing?

The primary objective of unit testing is to test each unit of software – including, but not limited to, a function, method, procedure, or object. A unit is essentially a part of the software that can be individually tested. Unit tests are conducted by developers to determine if there are any issues with a specific unit of coding by writing test cases to check the functional correctness of independent modules. However, if not done during the development phase, the QA engineers should be prepared to find more bugs!

Objectives of unit testing

The unit testing drives numerous objectives:

  • It divides the overall software into independent functionalities and isolates them.
  • It validates the correctness of the code.
  • It tests every procedure if it performs its function without any bugs.
  • It helps fix bugs early on in the development phase, saving time, cost, and effort.
  • It helps in garnering a holistic understanding of the codebase and prompts developers to make changes quickly and efficiently.
  • It helps document the product as the various tests performed on the units acts as the reference material. The unit tests performed on each module inform us about the appropriate and inappropriate use of the code.
  • It increases code reusability as the isolated unit of code will have been tested in independent environments.

Different types of unit testing?

There are two types of unit testing – manual and automated.

Manual testing:

In this case, each module is isolated and tested by a human being. It is time-consuming, cumbersome, and difficult, despite being hands-on.

The difficulty lies in isolating the code, as the units work together and the entire chunk of code will have to be tested. This makes it difficult to determine the root cause.

Manual testing is, however, more hands-on because it makes use of the customer’s (or the user’s) perspective.

Automated testing

Automated testing uses tools and scripts, being more efficient and precise while covering more units than possible with manual testing.

The automation in the unit testing follows a pattern that is more like a standard in this genre. One of the most fundamental and important patterns in unit testing is the AAA. It is a three-step practice where the test is divided into three sections: Arrange, Act, and Assert (AAA).

In the Arrange section, you set up the prerequisites like any stubs for external dependencies, any constant values, value changes, etc. Here objects, mock setup, and the expectations for the test are arranged.

In the Act section, the test is invoked, and the action takes place. The actual activity which we perform like applying a validation condition, and calling an external dependency takes place.

The Assert section ensures that all the expectations are met. We check for the expected result. For example, the value returned by the function is as expected or the validation is satisfied.

Example:

In the below React code example, we are going to check if the ‘Login’ heading and two images are present.

Here, it validates if the input fields, login button and ‘Create Account’ button are visible.

Here we validate if the value is not provided in the input fields. If no value is provided, then it will throw an error.

We check that if the ‘Create Account’ button is clicked the user is redirected to the ‘create account page’.

The following code snippet checks if the ‘Google login’ button is visible.

Common testing tools:

Listed below are some popularly used testing tools which provide a structured platform to automate the unit test cycle:

  1. Jest, a Javascript Testing framework built by Facebook designed for React-based apps but can be used to write automation scenarios for any Javascript-based codebase.
  2.  Junit, a unit testing framework for Java
  3.  NUnit, a unit testing framework based on.NET platform
  4. Karma, an open-source testing framework for JavaScript
  5. Jasmine, a free unit testing framework for JavaScript that uses behavior-driven testing
  6. EMMA, an open-source toolkit that measures Java Code Coverage
  7. Mocha, an open-source JavaScript Testing Framework that runs on Node.js

While unit testing can be manual and automated, TDD is one of the software development approaches, which gives utmost importance to unit testing.

What is TDD?

TDD (Test-driven development) is one of the crucial practices in extreme programming (XP), which in turn is one of Agile’s many development processes.

If religiously followed, TDD delivers the best outcome. While adopting this approach, test cases are written before the actual code, and then micro-iterations are conducted to ensure the best feedback is received using automated testing frameworks.

The TDD process is as follows:

  •       A single unit test is written before the actual program.
  •       The test is run; which fails due to the absence of the actual program.
  •       A program is written just enough for the test to pass.
  •       The code is refactored (or more features are added) to simplify the program.
  •       The tests are repeated and code is accumulated until the tests pass.

Following is an example of unit testing in TDD environment:

Step 1:  First, a single unit test is written; even before the actual program

Step 2: We run the test. The test will fail because the actual program is missing.

Step 3:  We write the program just enough for the test to pass.

Step 4: We refactor or add more features to the code to simplify the program.

Step 5:  Repeat running the tests and accumulating code until the tests pass.

All the tests will be passed successfully.

Now let’s look at some of the best practices we should follow as part of the unit testing process to get maximum efficiency and best results.

Unit Testing – Best Practices

The following best practices in unit testing will ensure that your test cases are simple, straightforward, and maintainable.

As explained in the earlier section, the AAA method clearly separates the test setup and the validation steps, giving a good structure to the test case.

Follow a standard naming practice for test classes

To have a clear distinction between the code that organizes the test, the code that tests, and the code that is being tested, a standard naming convention should be followed:

  • The original class and the source code class name should be the same.
  • The test class name should be the same as the original class but with the word Test appended.
  • The class that organizes the test should have the word TestSuite appended to it.

Test one code at a time

You need to ensure that the smallest unit of the code is tested. If the units are interlinked, then the changes in one unit after the test can reflect in the other.

Moreover, when the code is run, all the tests are run in one go. Since the order in which the tests are run cannot be determined, the test on one unit cannot be linked to the other.

Avoid active API calls

Many a time, you might come across API calls to databases and other service calls which should not be included in the tests. However, if they are not engaged in the tests, ensure that they are not active when you run the tests. It is always better to provide stubs for the APIs with expected responses and behavior and focus on testing only the unit in perspective.

Test cases should have a separate repository

The test cases and the source code should be stored in different directories and should be distributed separately. The best practice is to have similar directory paths for source code and test cases to keep the development process neat and clear. This may vary depending on the technology and project guidelines but should maintain consistency.

Minimize or avoid manual intervention

It is best to avoid manual verification by the developers. As a norm, the developers use debugging statements like console.log which should be avoided. Instead, they should be rewritten for automatic validation. Furthermore, complete automation also informs the user of the tester’s intentions.

The automatic verification is as follows:

Choose a good testing practice

Different types of unit testing practices like TDD, mutation testing, and behavior-driven programming, are more or less conducted parallel to the source code of the application. These test practices help in reviewing the tests and the code simultaneously.

Write tests not to pass but to detect bugs

The best practice for writing test cases is to write dependable tests. The idea of unit testing is not to pass the test but to report bugs when things go wrong. The tests should ideally display clear error messages.

Single assertion per test method

An assert is to test a particular action of software. It is a Boolean value that returns true or false based on the hypothesis. It is best to limit the assertions per test method to one. If there are say ten assertions in a test method and one fails, it will be difficult to track it down. This practice saves effort in the longer run.

No code untested – Go for 100% code coverage

Set a goal to cover 100% code during unit testing. The developers should break the code into smaller components and aim for complete code coverage. Even 60-80% is a good prospect as 100% code coverage might not be doable in every software. The gaps in the unit testing can be fulfilled by integration testing.

Benefits of unit testing

The following are the advantages of performing unit testing early in coding.

Catching defects early with unit testing

Catching the bugs at the early stage of the development process saves time and cost for the stakeholders. It becomes a tedious task for developers as well if the bugs are not caught early because the bugs tend to increase over time. And fixing the bugs at a later time can prove to be extremely hectic. Hence unit testing is a highly recommended practice for developers.

Documentation

It is universally accepted that testing is the best form of documentation that can be run and validated. The unit tests can be run at a later stage to check if the source code is performing the same as it was when it was written.

Unit tests also express the intention of the developer about what they hope to achieve from the code.

Enables safer refactoring

Refactoring is the adjustment(s) made in the code as the project becomes increasingly complex since more bugs crop up with the changes in the code. The function of a set of unit tests is to keep a check on the bugs from entering the code. With TDD unit testing, minimal code is written for the tests to pass. Refactoring on this minimal code becomes easier which once again reinstates the importance of unit testing.

Offers simplified development

Unit tests reduce the need for hotfixes as the bugs are found early in the development and can be fixed as they are detected. Now, what are hotfixes?

Many a time, the bugs are not detected early on in the development process and creep up into the later stages of development. These bugs need to be fixed immediately, and such corrections are known as hotfixes. The downside is that hotfixes produce unwanted results when the tests are run.

Minimal regression testing

Regression testing is where any new changes in the code, after certain adjustments, are tested using all (or a limited set) of tests.

Regression tests are usually a mixture of unit tests and integration tests. The better the unit tests, the easier the regression testing.

Writing unit tests can be tedious sometimes and requires extra effort, which if done right, gets justified by the advantages it provides.  

Unit Testing and its limitations

We have seen the necessities of unit testing, the objectives, and the benefits of unit testing. Now, let us see the reasons why many developers shy away from the practice.

It is time-consuming

Writing unit tests is time-consuming, especially if you expect complete code coverage. As a workaround, you can avoid writing unit tests for an obvious piece of code.

Bugs multiplied in production

Sometimes there are cases when the bugs are suppressed during the unit testing phase. If the bug surfaces in the production code during the production phase, it will make an appearance in the unit test cases as well.

For example – a developer writes code for resetting the password for a website without logging in and has written test cases where the user automatically logs in 99% of the time. There is one test case where the code is written to avoid automatic login and the user is redirected to the password reset page. Here, the bug is suppressed and the user automatically logs in. Then there are high chances that if the bug surfaces in the production code, it occurs in the unit testing code as well even if it was hidden before.

Unit test does not test code dependency

When the unit test checks distinct units, it does not check the dependencies between the units. For example, if one unit takes the input from the user and another unit displays the output taken from the user, the unit test will not check the link between the two units. We will then need to wait for the integration test to check for any bugs.

What’s next?

Unit testing is the the first step towards quality assurance across the software development lifecycle, which can be automated. For a complete suite of automated testing solutions, take a gander at our no-code test automation platform that is helping businesses automate processes – with fewer specialized resources.

SCHEDULE A CONSULTATION

Leave a comment

Privacy Preferences
When you visit our website, it may store information through your browser from specific services, usually in form of cookies. Here you can change your privacy preferences. Please note that blocking some types of cookies may impact your experience on our website and the services we offer.