How unit testing may prevent a disaster in computer systems?

Mauricio Morales

Software Developer

July 28, 2020

8 min read

When software developers start our professional path, we generally think about how fun it will be writing code to solve any type of problem or satisfy any needs our customers have. In pursuing such desire, our priorities are usually learning different programming languages, frameworks, design patterns, good practices, as well as other concepts, techniques and tools that will help us to accelerate and optimize code writing and functioning.

However, an area that is quite forgotten, especially by startups and small companies in the software development industry, is precisely the establishment of tools allowing for writing, executing and measuring unit and integration testing; many people think that they result in a loss of time. In this article, I will explain why running this type of tests should be an essential part of the routine and discipline of a software developer. To achieve this, it is important to first understand certain concepts and, why not, to write a couple of tests.

TDD

When we are discussing unit testing, it is necessary to talk about TDD, and acronym for “Test Drive Development”. This is an approach that allows developers to write code based on tests that have been previously written. This, with the purpose of making code writing (complying with the test) a natural and fast development. A TDD has the following workflow:

Unit Testing Flow Chart

As you can see in the chart, a TDD is quite strict regarding the order of what we must do for writing a test. Personally, I do not follow this order rigorously because I consider that each developer can adapt this process to their workflow.

For example, in my day to day, I write code as follows:

  • I define the method that I will implement in its respective interface.
  • I write the implementation of the method, so that it returns the response that I expect, regardless of any internal logic.
  • I write the test using super basic assertions, to ensure that the result is not indefinite.
  • I integrate the logic of what my method will do, step by step. At the same time, I add what I need to my test; for example, if my first step is searching for a record in the database, I will make the respective change in the test to simulate that response, and then I will adjust my assertions according to the problem that I’m trying to solve.
  • The step above will become a loop until the complete logic is obtained, similarly when there are conditions or errors that I should test. Finally, I will have to design additional tests to check such cases.
  • Finally, I will adjust the assertions in my tests and perform a final analysis, in case I have to make a small refactoring in my tests or in the code itself.

You can write high-quality tests as long as you have in mind what you want to test and why. At the beginning, they could be dark concepts, but will be clarified as you gain practice and experience.

Unit Testing

It is the validation of the smallest fragment of code, which can be a method or a function or a small class; this will depend a lot on the context where we are located.

Below, I will describe in detail how to write a unit test using Mocha framework and an assertion library, Chai, in the backend execution environment of Javascript, Node.js.

We will initialize our project in Node, using the following command from the terminal located in the directory that we have selected.

bash npm init

This command will ask us several things regarding our project, for this example, we will maintain the default values. The important point is that, as a result of the execution of this command, we will obtain the package.json file within the root file of our directory.

json { “name”: “unittesting”, “version”: “1.0.0”, “description”: “simple examples about unit testing with mocha on node.js”, “main”: “index.js”, “scripts”: { “test”: “echo ”Error: no test specified” && exit 1” }, “author”: "", “license”: “ISC” }

Then, we install Mocha and Chai as development units.

bash $ npm install —save-dev mocha chai

We create in the file service.spec.js within the src directory in the root directory of the project. We write one test with the purpose of creating a function that, after receiving the greetType parameter, if the value is 1, the result of its execution is a friendly greeting with this text: “Hello, how are you sir?” Then, we design another test so that the result, for any other value, will be: “Hi, What’s up bro”.

describe("Unit Test Examples", function() { it("getMessage should greet kindly", function() { const greetType = 1; const response = service.greet(greetType);


expect(response).to.be.eqls(`Hello, how are you sir?`);

});


it("getMessage should greet", function() { const greetType = 1; const response = service.greet(greetType);


expect(response).to.be.eqls(`Hi, What's up bro`);

}); }); ```


In this fragment, we import our service.js file, that will contain our getMessage method, then we use describe and it, which are provided by Mocha, to encapsulate our assertions and to write our test. They receive a first parameter that is a description of what they contain, namely, what we are going to test, and a function that will be responsible for executing our tests.


We execute our tests by executing the command in the terminal.


bash $ npx mocha ./service.spec.js


The result, as we have expected, is an error, since we have not yet written the code for the function nor exported it from the service.js file.


```bash Unit Test Examples 1) getMessage should greet kindly 2) getMessage should greet


0 passing (20ms) 2 failing


1) Unit Test Examples getMessage should greet kindly: TypeError: service.greet is not a function at Context.(src\service.spec.js:7:34)


2) Unit Test Examples getMessage should greet: TypeError: service.greet is not a function at Context.(src\service.spec.js:14:34) ```


Type the code of the function that complies with the test in the service.js file within the src directory.


javascript module.exports = { greet: function (greetType) { if(greetType === 1) return `Hello, how are you sir?`; else return `Hi, What's up bro`; } };


In this fragment, we simply export the greet function, which receives as an argument a number and returns a message based on that number.


We will execute the test again.


bash $ npx mocha ./service.spec.js


Now we will obtain a satisfactory result.


```bash Unit Test Examples √ getMessage should greet kindly √ getMessage should greet


2 passing (26ms) ```


Let's suppose that we made a code refactorization, and this time, the number that triggers the kind greeting will be 3.


javascript module.exports = { greet: function (greetType) { if(greetType === 3) return `Hello, how are you sir?`; else return `Hi, What's up bro`; } };


We will execute the test again.


```bash Unit Test Examples 1) getMessage should greet kindly √ getMessage should greet


 1 passing (23ms)
 1 failing
 1) Unit Test Examples
 getMessage should greet kindly:
 AssertionError: expected 'Hi, What\'s up bro' to deeply equal 'Hello, how are you sir?'
 + expected - actual
 + -Hi, What's up bro
 + +Hello, how are you sir?

And, as we might expect, this time one of the tests has failed due to the changes that we have made in our function.

With this small exercise, we begin to understand what we obtain by writing unit tests for our code.

Benefits of Performing Unit Tests

  • Speed. Today, computer systems change faster with time, so they must be adaptable to the requirements they’re trying to comply. For example, a simple system for image processing probably will require more processable formats. Unit tests will help us to prevent that any changes that we make in our code affect the use cases that are working without errors or bugs.
  • Quality. For any serious company in the software development business or that depends on software developed in-house, it is crucial to ensure the quality of the code of its applications. Unit tests act as a sieve that will allow us to filter the greatest quantity of potential bugs within their modules, and to correct them in time. Evidently, we will not be able to reduce all these incidents entirely, but this will mitigate them in a large percentage.
  • Makes code analysis and debugging easier. Unit tests can help us to debug our code in a much simpler way. If we have errors in the production environment, and we don’t know their cause, we can start executing tests in a local environment and play with the parameters that could be generating the error. This saves us time because we do not have to launch a patch without even knowing if it is going to work.
  • Design. When we think about the things that we have to test, planning the future design of our code is also a side effect. In this case, we will ask ourselves: Should this method go within this class? or, can I disconnect this method using a static class?. The answers to these questions give us feedback to improve the architecture and design of our code, which in turn will result in greater legibility and maintainability for the code.
  • Resource saving. In all industries, time and money are capital factors. The benefits described above are translated into a decrease in the time that developers use to solve bugs, perform changes, adaptations or improvements. This, in turn, allows them to be more productive and generate greater value for the company.

An Advice, from Developer to Developer

When I started my professional career, I never focused in anything more than writing the code that I needed to solve the problems that my customer’s applications required. Most of the time, I spent hours manually testing code to solve bugs or to apply changes to my code. So, one of the biggest challenges that I had to face when I started working in Kushki was adapting to the strict scheme that the company had in place concerning unit tests, as the smallest line of code must be tested.

Thus, I learned that, as developers, we have to invest a great percentage of our time in learning how to write tests that generate value for our work teams, our projects, employers and customers. Searching for new testing tools that provide us greater speed, quality, efficiency, security and maintainability is fundamental. The language in which we learn to write these tests is not so important because when technology changes, we will already have strong foundations to be able to migrate without headaches.

Conclusion

There is an infinite number of technologies and tools that allow us to write unit tests; each work team is responsible for searching and choosing the ones that better adapt to their workflow and to the objectives they try to fulfill, as well as to the different programming languages available in the market of this industry.

Even if the concept of unit test is small and concise, it is an extremely powerful resource. As we have seen, there is no excuse to not write unit tests for our projects. Benefits can be noticed from the first moment that we have decided to apply them. Tests help us to understand better the technologies that we are using and the problems that we are solving.

In an extreme case, our unit tests can save us from a total disaster; just imagine that a pair of zeros were added or eliminated in a bank transaction, and that this error was executed millions of times before being detected.

Would you like to keep up to date with our content? Subscribe to our mailing list.