How to Write an Effective Unit Test in 8 Paths?

ibrahimcanerdogan
6 min readNov 16, 2022

--

Photo by James Wainscoat on Unsplash

Unit tests have an important in the development of projects. It provides a higher quality contribution to the development process, the person and ultimately the project. Confidently implementing new features allows existing functionality flaws to be noticed and fixed before moving forward. How do we make this important component of the software more effective? Let’s have a look.

AAA (Arrange, Act, Assert) Approach

It is a general approach to writing more readable unit tests.

Arrange is where you set variables, initialize objects, and set up the test to run. Calculating the required inputs to provide the expected output takes a long time because of understanding the logic and advancing the beginning according to the function to be tested. But it is the stage that lays the foundation.

It is the stage where the function to be tested is called in the Act stage. Objects prepared in the Arrange stage are used at this stage. If an output is obtained as a result of the function, this output is assigned to a variable for comparison at this stage.

At the assert stage, it is tested whether the operation you have set up is correct. Control can be performed or “Verified” between “Excepted” and “Actual”. That’s the essence of unit testing, as this section finally tests something.

Tests Should Be Keeped Short

Short functions are much easier to read and understand. One piece of logic (if, else, etc.) should be tested at a time, so the tests should not be too long anyway.
Due to the high dependency in the arrange phase in some of the tested functions, the use of mock and stub will increase the number of codes in the unit test. This may be an indication that there is a problem with the code design.

Copy-paste logic should not be applied in the tests. It is based on the principle of “Don’t Repeat Yourself” as it is not different from a normal code structure.

It should be noted that after a while, the test code is not dealt with. “Composition” should be used instead of “inheritance” because it is updated in the tests as the process changes within the project. Composition is one of the cornerstones of Object Oriented Programming. There is an ownership (has-a) relationship in Composition. The basic structure here; To ensure that the created classes can be used within each other. In this way, code duplication and code pollution are also prevented.

Photo by Felipe Labate on Unsplash

Set Testing Priority

First of all, the main purpose of the function should be determined and the appropriate test should be written. In the Arrange stage, the correct operation of the function takes priority when all conditions are met. This test case is seen as much more readable and simple in the codes of the function to be tested.
After that, tests should be sent according to the extreme cases in a deeper way. Things that don’t happen very often i.e. incorrectly entered value, missing argument, null data, calls are exceptions to functions etc. situations should be determined and necessary preparations should be made accordingly.

Testing impossible scenarios and aiming for 100% test coverage is a waste of time if you’re not programming a shuttle to Mars.

Test Should Be Done Before The Error is Fixed

Once you find code that is not working as it should, consider writing the test that reproduces this error. It will be much faster to fix the test by debugging it separately from the rest of the application code.
In the next step, it will be sent to the function regression test. And when the previously failed test starts to pass, it will be certain that it is functioning properly.

Deterministic Tests Should Be Developed

In short, deterministic systems do not have randomness in the development of the future states of the system. In order to prevent a randomly correct result, the uncertainties within the system should be removed. Uncertainties often arise with external dependencies of functions. Reasons such as an interruption on the API side and busy database affect the test results.
For example, using Date.now() moves the code away from the deterministic system, as it will do something based on the current time. Everything in the test code should be stabilized with a mock or stub to make it reliable.

Photo by Max Wagner on Unsplash

Test Naming

Test functions, unlike other functions, perform a more specific situation by giving certain parameters among many conditions. Accordingly, in naming, the situation intended to be tested should be explained clearly, but naming is easier.
Test functions, which can also be named as strings in double single quotes format, offer great freedom to the developer. If no standard has been accepted in the affiliated company, institution or organization, function names can be given in String format. It is appropriate to give this situation in maximum explanation and a little more summary.
It should provide enough information to understand exactly what is failing and what it is trying to do. For example, ‘control Func() return 0 for an foreign customers’ is a pretty self-explanatory designation.

Small Pieces of Code

In unit tests, the goal should be to help us quickly find and isolate broken pieces of code. That’s why classes or functions should be created from mock or stub dependencies.
The more paths you get tested in your tests, the harder it is to have solid coverage and fail. When something breaks, these tests will also fail and you will be able to detect the problem quickly.
So don’t test the whole method at once. While meeting the basic requirements, the code becomes much simpler. You can choose a more specific name, the test can have less code, it’s even easier to read and update.

Test Main Functions

This issue can be evaluated within the scope of prioritization a little more, but testing the main functions as a priority avoids the test crowd. Writing fewer tests provides significant contributions to the developer in terms of cost and time.
When we think about it, mostly some functions may have functions that are run exclusively for them and that are not used in any part of the project. In this case, these functions are inaccessible to the test classes because a “private” definition is highly preferred for functions that are private to a function. These functions should be tested separately, additional test functions should be created and the effect of the “private” feature should be specifically removed for tests. In both cases, it causes extra code and waste of time.
In such a case, the main function is given the necessary conditions at the arrange stage according to the special function. It is then called in the act phase. The outputs of the main function are checked according to the required conditions of the special function. In this way, the function is “covered” without writing a separate test.

Conclusion

Writing good test code makes it essential to consider many different situations. This way, bug detection, refactoring, and code maintenance are simplified as long as unit tests are written. The analyzes made by means of unit tests provide great benefits for the future by concluding the subjects faster, passing the regression tests successfully, and revealing more healthy functioning functions during the development process.

IBRAHIM CAN ERDOGAN

Linkedin: https://www.linkedin.com/in/ibrahimcanerdogan/

--

--

ibrahimcanerdogan
ibrahimcanerdogan

Written by ibrahimcanerdogan

Hi, My name is Ibrahim, I am developing ebebek android app within Ebebek. I publish various articles in the field of programming and self-improvement.

No responses yet