October 14, 2019

Quality assurance — or why you should pay for what you can’t see

Quality assurance — or why you should pay for what you can’t see

Just like you never notice the cleaning service when they do a good job, you never notice a software program when it is working smoothly. But unlike a cleaning service, software quality assurance must be ensured by heavy automation and, more importantly, by constant maintenance. If you invest in software quality assurance you don’t get silver bullets to solve all the problems, but without it, you are doomed to having permanent issues and problems throughout the life of your product.

What is quality assurance?

As Wikipedia defines it:

Quality assurance (QA) is a way of preventing mistakes and defects in manufactured products and avoiding problems when delivering products or services to customers

In the context of a software development (and web development in particular), this means manually testing the program before releasing it to production. It also means writing a facility code that continuously and automatically checks parts of a codebase (or the entire project). Manual testing is an essential part of your development process, so there is no need to explain what it is. What then is this other thing that I called a facility code?

This is also known as unit testing and functional testing (actually, there are more kinds of testing approaches, but they are not covered in this blog post). Each serve different purposes. Unit testing deals with small units of a codebase (like functions or classes), and it can be initiated quickly and immediately to let a developer know if anything breaks after he makes his changes. Thus it allows developers to quickly iterate over small portions of a codebase and feel safe about the changes they introduce while coding. Functional testing, on the other hand, is meant to check if real-life user behavior scenarios applied to a project produce the expected results. They don’t deal with code, but rather with an interface of a program that is exposed to a user.

Unit tests are coded manually and are usually accompanied by a new functionality (in an ideal world). They are often run by developers every time they want to check if the changes in one part of a program introduces bugs to other parts of a program. Functional tests can also be coded manually, but there are software tools (like Ghost Inspector) that records user actions right from a browser and then continuously replays the actions on a running project and validates a result. Functional tests are run less often, usually before releasing a bunch of serious changes.

The typical coding process flow with unit testing
The typical coding process flow with unit testing

Why you need to automate it

If your project has been constantly evolving, then at some point it becomes a complex mechanism with a lot of interconnected parts, reusable components, numerous customizations, and “special tweaks”. Sound familiar? Every change to the codebase is a potential bug. It’s not rare to see one line change (even one symbol!) that has an impact on core components and leads to a critical bug that disrupts a business logic flow. If you want to find and fix all bugs before bringing a new release to your users (and you should!), you want to make the quality assurance process as quick and automated as possible. You want to check everything, or at least as much as possible. Doing this manually is not just hard, it is impossible.

Automation helps when you expand your team of developers. With unit tests that can be run very quickly after any change to the codebase, your developers become more aware of bugs they can potentially introduce to any part of a project. There is no need to discuss any issue with them in person. Unit testing framework will point out exactly which part of the codebase fails after changes were introduced by a developer.

While unit testing helps your team avoid local bugs, functional testing comes into play when you want to make sure that the general logic of business processes works properly with your users. A functional testing framework automatically tests real-life user behavior scenarios within a project. For example, it can simulate how a user signs up on your website — it will “visit” the website and click all the buttons and links according to a predefined script and will assert in case of error or any other unpredicted situation. Normally you need to make sure that all functional tests pass before deploying a new release into production, but it is also a normal practice to run them after a developer pushes a new code into a code repository.

Drawbacks

Quality assurance takes time, and this will have an impact on your deadlines. Unit tests and functional tests must be first coded before they can be run automatically or on a developer request. It’s also worth mentioning that nobody likes writing tests for a code. Why? Because the reason for writing tests is not obvious at first glance.It is not going to finish a task, but rather it is going to help other developers to avoid mistakes in the future. Sounds like a waste of time when you’re facing down a deadline, right? Yeah, exactly!

Coding tests is a time-consuming process because of the necessity of making a complete recreation of a runtime environment. Vast majority of modern web apps require database and caching systems, and sometimes they need a distributed tasks system. These things often must be considered by unit tests and simulated in an isolated environment. It is very similar to scaffolding. It is tedious and not often a straightforward process.

To recap everything above — automating quality assurance is a long-term investment, and it will never bring immediate results. Over the evolution of a project, however, automated tests will catch more and more bugs, preventing them from being exposed to your users and keeping your project robust (!) and stable.

How to start

It’s never too late to start doing things the right way. If your project is not covered by unit tests at all, but is in the very early stages, then you can start writing tests as soon as you begin developing a new feature. You can come back to the old code later. In the beginning of a project, there should not be too many things to cover up with unit tests. Leave the tests for old code for the better times and make sure that any new code gets covered by at least a couple of unit and functional tests.

However, in the case of big old projects with tons of legacy code that is poorly covered by tests (or not covered at all), the situation is much worse. Along with suggesting that you force your team to strictly cover any new feature with unit and functional tests, I would also highly recommend starting to write tests for the common pieces of code (like functions or classes) as soon as possible. Generic or common things applied in multiple places of a codebase with different params can be quite sensitive parts of your project. Every time developers modify a common code, they need to test all the applications of this code in a project. Performing such testing manually is extremely time-consuming!

Finally, don’t forget about fixing bugs — this is the main reason why you should invest in quality assurance. A good practice is to write a unit test it reproduces a newly found bug even before fixing the bug. This way when the unit test passes, developers know for sure that the bug is fixed. They also stay confident that the bug will not appear again in the future as it gets covered by a unit test.

Artem Rudenko
Artem Rudenko
Software engineer, founder of ottofeller.com

Let's build a thing together!