The basics of automated software testing

Dominik Szczepaniak
7 min readMar 22, 2020

In this article, I’ll introduce you to the topic of automated software testing. I’ll show you also how to write your first test suites in JavaScript.

The article has been originally posted on my Polish blog — devszczepaniak.pl

By most beginner programmers, the topic of automated software testing is skipped or at least neglected. Partly, I understand their motivations. For the beginners, automated testing may not have any sense — they have done it already manually. Their software works and by the way tests are hard and are a waste of time. I have to point it now — writing tests isn’t a challenge if our code is written according to good practices. If our core is chaotic and doesn’t keep the good coding standards, testing it can be a serious problem. On the other hand, writing code dedicated to tests is also a mistake. You need to find a happy medium.

Why test?

To appreciate the value of automated tests I’ll provide you a bunch of their advantages:

  • For the first, we ensure the code works as you expect. Very often we can test it manually. The problem appears when manual testing becomes very time-consuming. For example, let’s take a function that as an argument data in many different formats. After changes in this function, you should test this function manually for every format. In this particular case, manual testing is, in fact, a waste of time.
  • Tests can be additional documentation for your code. Of course, it cannot be a replacement for official documentation and documentation tools like JSDoc. However, tests can be a supplement. In a test suite, you can see what kind of data is passed to a tested method and what result is expected. In addition, every test suite should contain a description, which also could be helpful. It becomes especially useful when you need to introduce a new person to the project.
  • Tests help to avoid regression — breaking a previously working code. It is pretty often case when a little change in one place breaks the other part of an application. Thanks to tests it is possible to detect it on a development stage. It is always to get information from tests than from a customer. You don’t have a guarantee you fully reduced the risk of regression. However, the chance of regression is significantly lower.
  • Tests are a marketing advantage and are also proof of the quality of our product. Scrupulously tested software can be a real advantage in the software market. On the market, where you need to gain and retain customers. From the customer point of view, tests are a huge benefit. Thanks to tests, customers trust you more. It is worth to mention that tests may be a compulsory factor to make a deal with some customers.
  • Tests help to save time for manual testers. Time is money — work time of tester isn’t cheap. Tests can reduce the time testers need to spend to fully test your software after every change. Without automated tests, manual testers need to test all details that should be covered by automated test suites.

The test pyramid

The basic types of tests are described by a popular schema called the test pyramid:

Unit tests

At the very bottom of the pyramid are unit tests. These tests are responsible for testing very small pieces of code like methods or functions. They should focus on testing this exact part of a code. That’s why all external elements e.g. repositories, controllers, etc. should be mocked. There is a reason why unit tests are at the bottom of the pyramid. In most projects, the number of unit tests is higher than the number of other kinds of tests.

Integration tests

The next kind of test is the integration tests. They are responsible for testing integration between components. They should verify whether the component works as we expect. There you should use the real repositories and controllers instances instead of mocks. If you want to see how valuable integration tests are, google the “2 unit tests 0 integration tests” phrase. You will get a bunch of funny pictures where integration tests were missing.

End-to-end tests (e2e)

Last but not least are end-to-end tests called also e2e. Their responsibility is to check the whole responsibilities and processes in an application. Now, you don’t care about what is a result of some function or if a function was called or not. These tests check e.g. a post was added, a comment was removed, an order changed its status and so on. These processes can be built with even hundreds of methods but e2e tests check only the final result.

AAA pattern

Before you write your first test it is worth learning the pattern that helps to write tests in an exact schema. For this purpose, there is a pattern called
AAA — Arrange, Act, Assert.

This pattern assumes tests consist of three parts:

  1. Arrange — in this part, you need to set up the test environment. There you should create mocks, required objects, and other needed variables and structures.
  2. Act — this part contains the test scenario. There is the actual use of the tested code.
  3. Assert — the phase responsible for checking the received result. In this step, you need to verify if the received result equals to expected result or whether a method was called or not.

It is worth to avoid sequences like arrange -> act -> assert -> act -> assert. It is not a huge mistake but one test suite should cover only one test case. These sequences can be split into two separate tests.

Testing tools in JavaScript

For creating tests there are a lot of useful tools. Before you write your first test it is good to get know a list of useful tools:

  • Test runners: Mocha, Jest, Jasmine,
  • Assertions: Chai,
  • Mocks: Sinon,
  • Code coverage: Istanbul.

The real example

To fully illustrate the topic of automated software testing, I’ll show you how a very simple set of unit tests can look. The first step is to create some function that will be tested:

The function above receives two parameters and depends on the second parameter its behavior is different.

After creating a piece of code I need to test it. I have to prepare the test environment, create test suites and verify received results. To complete this step I used previously mentioned Mocha, Sinon, and Chai:

This piece of code needs a few words of explanation. In the beginning, I import needed dependencies and the tested function. Next, I create describes — wrappers for test suites. Describe() can be nested — it allows to group test cases related to the same part of code.

Before the test — beforeEach

The next step is beforeEach() function. The whole code inside of this function will be invoked before every test suite for given describe. Thanks to beforeEach, the first step — arrange — is written only once. Another useful function is afterEach() which is invoked after every test suite. It is useful e.g. closing a connection with a database or stopping processes. There are also before() and after() functions that are invoked only once for every describe().

In beforeEach() I created mock — the “false” function that imitates the real dependency. In this particular case, I’ll use mock a repository to check whether its method was called.

Using beforeEach() has also one more advantage. The mock is created anew before every test suite. If I created mock only once, I’d be able to read a result of one test in another test. It is a bad practice. Every test case should be separated and don’t have an impact on others.

Test case — it

A further step is to define exact test suites. For this purpose, I used it() function. As a first argument, I provided a test case description. The second argument is a function that includes the invoked code. To fully test the function I’ve created three test cases. In my opinion, there is no need to describe in detail every test suite. Their descriptions are sufficient. That’s why the description is important. If you take a look at descriptions every description starts from “should” — it is a good practice.

If you analyze the structure of each test you will see the two elements of AAA pattern — act and assert. Firstly, the real function is invoked ( act ). Next, there is a verification of the received results and whether the function was invoked ( assert ).

Summary

I hope, after reading this article you are a bit more familiar with automated software testing. I think the return of investment from writing tests is tremendous. It also has a positive impact on coding comfort. I encourage you to try it yourself and read the sources.

Sources:

--

--

Dominik Szczepaniak

Professionally Software Engineer at CKSource. Privately a blogger, a fan of Italian cuisine, and a fan of cycling and weight training.