Write Rock Solid Tests in Any Programming Language

Every software company wants to produce quality software that is trusted, meets the client demands and is delivered on time.

Bruno Lombardi
Bits and Pieces

--

Photo by Photoholgic on Unsplash

Software testing should be designed, engineered and monitored. The less bugs and defects a software has in its initial stages of development, the less the maintenance cost after it is in production.

In most cases, tests requires more work than any part of software engineering. If done casually and without strategy, then all the effort, energy and time doing it are wasted. With this in mind, how and why one should do software testing? And how can you do it in any programming language?

The purpose of this article is not to tell you what tools, frameworks or test environments you should be using, instead, we want to focus on how we should be testing our software.

Of course there are many different types of software, with different architectures, written in different programming languages in different styles and paradigms.

Nevertheless, I believe we learn more with practical examples, so I will try to show some short examples in my favorite programming environment: Node.js.

3 Truths About Software Testing

I want to share my experience and also my opinion on this subject, and I would love for you to complement this information.

Of course there are many strategies and patterns for software testing in literature, but in general, they all share some principles, or truths:

  • You can't test a software completely
  • Tests should prove that bugs exist, not the opposite
  • Tests start in the component level, and evolve to the integration level

It does not matter what strategy we are using, a test should verify that a software does what it is expected to do, attending it's requirements, and don't do what it is not expected to do.

A test case should try to discover the many different situations a software behaves, and point out what is undesirable, incorrect or unspecified.

Most of the time, test cases simulate bad input, unexpected user behavior, errors in external dependencies, and many other negative scenarios expecting that the software keeps working under acceptable condition.

Applying these principles in the real world

The first aspect of software testing is to know what to test. Let's start start with the simple case of an online shop's favorite products list. We can define some initial functional requirements as follows:

  • A client can have a list with favorite products associated with him, but that is not required. All the products in his list must exist in the shop.
  • A client cannot favorite the same product twice.
  • A client can unfavorite a product at any time, this should remove the product from the list.

I will try to reproduce this example in JavaScript with Node.js. We are not focusing on the tools, architecture or frameworks used here, instead, we want to make sure we are writing good test cases and that the software behaves as specified in the requirements.

To make it even more practical, I am going to share a link to a CodeSandbox project and some code snippets below.

The ShopService module, very simple, as I said, we are writing a super simple service

This module is responsible for holding our shop's products, as you can see, we are just hard coding data, as our focus here is writing tests.

Now, let's see another module, the ClientService.

A little bit more complicated, this module is responsible for holding clients, and updating their favorite products

The client service module is also very simple, and I chose to make it responsible for adding and removing favorite products from clients.

Now, how do we test all this, to make sure it is attending our requirements?

There is the test case I wrote for this example below, (read comments in the code for detailed explanations):

Test cases for Favorite Products

If you run these test cases, you are going to see they are all passing, as shown in the screenshot below:

In the example above, we are writing unit tests, they test the functionality, our goal is to run every statement and condition of our functions and make sure we are meeting the requirements.

There are some techniques we can use to write functional tests, and in this example, we are using 3 techniques, let's see each one of them:

Control tests

The goal of the test case is to make sure all the data is correct, that every transaction is authorized, that the input and output data are valid and attending the requirements.

One example of a control test is shown below:

Example of a control test

Here, we make sure that an existing client can favorite a product, if the product exists. Then, we also make sure that the list of favorite products of that client now contains the product we favorited. Don't forget, our tests are very simple, and there could be more complex logic here, more business or security rules to be tested.

Integration tests

The goal is to guarantee that one or more units of code, when combined, work as expected. See one example below:

If you have experience writing integration tests, you may be asking: isn't this a unit test? Yes.

But how is this both a unit test and an integration test? Let's see what we are testing again: when calling the function addFavoriteProduct, we want to make sure we call getClient function with the correct parameter, the client id.

Aren't we testing two units of code? Yes. What's an unit of code? A function? A block of code? That's on you, but I think it is pretty reasonable to say that we are testing the integration between two functions here. Let's move forward.

Error treatment tests

The goal is to make sure the system can handle unauthorized or invalid transactions appropriately, to validate that all the error conditions are recognized by the system and processed accordingly. One example is seen below:

I chose to return a null value when something wrong occurs. In the test above, we try to add a favorite product that do not exist, and in that case we return null, which we consider to be an error treatment. We could throw an Error, but I preferred to keep it more simple in this case.

The earlier you start, the less it will cost

In this simple article, we discussed how testing is important in the software development process, we thought about what would happen if we do not test a software, and finally, we saw some software testing principles and techniques, and put them to practice with a simple example in Node.js.

I hope you enjoyed the content and techniques we've discussed here. If you have any suggestions, critics or comments, you are free to post below. Thanks!

Build composable web applications

Don’t build web monoliths. Use Bit to create and compose decoupled software components — in your favorite frameworks like React or Node. Build scalable frontends and backends with a powerful and enjoyable dev experience.

Bring your team to Bit Cloud to host and collaborate on components together, and greatly speed up, scale, and standardize development as a team. Start with composable frontends like a Design System or Micro Frontends, or explore the composable backend. Give it a try →

Learn More

--

--