The word testing can be somewhat daunting to developers, because it means that for each important feature that is created a test should be written that tests the correct scenarios from the incorrect ones. Some developers and even companies do not see a good return on investment in writing tests, so they don't write any tests at all!
What makes developers dislike writing tests is the process of creating these tests, so this obviously adds to the development time, and time is a limited resource. What I have found is that many developers have a misconception about testing in general, what kinds of tests, what to test and how much time you should be investing in writing tests.
In this short article I'd like to go over what testing is, the different categories of tests, what to test and finally give you some pointers in how much time you should dedicate to testing.
Writing tests can be a pain at times, and in some cases the amount of work involved in writing them cannot be justified. However, there are several important reasons why you should write tests, I'll list a couple here:
For me the first benefit alone is worth writing tests, especially in large code bases which have multiple developers working on it. In my experience, well written tests can help in catching bugs and errors early when adding features or refactoring code. Now, a counter argument that some make is that it takes time to write tests, time that can be used for other things.
But one thing that is overlooked by those that are against writing tests, is the amount of time you will spend debugging your app because of a change that was made that broke a core feature.
More than likely, had there been tests for the imporant core features of the app, the tests would have caught this bug and you could easily track down the change that broke things. So essentialy, writing tests is investing early and then reaping the reward in an ongoing manner as you add features and refactor code.
The second main benefit of writing tests is that it acts as a form of documentation for your app, so when a new team member comes along the can have a look at the test and see what a particular part of the app should do. In my experience, when I join a new team I try to look at the tests that are in place, if there are any, and I found that this helps me in understanding the code base better.
One misconceprion when it comes to testing that I have come across is that you have to test everything in the app, this is not possible and you only want to add valuable tests. You should test the relevant parts of the application. When I say relevant things I mean things like:
It's not possible to cover every corner of an app, especially large and complex ones, so instead of not writing tests because you can't write tests for the entire application, write tests for the important parts and have a test priority policy.
There are a whole host of different types of tests that you will come across, and sometimes these can be confusing. I'll list the main categories of tests:
A unit test is the lowest level of testing that can be done. This type of test validates a single function or single component within an application. For example, say you have a function that replaces a camel case string like 'MidnightBlue' with spaces to 'Midnight Blue'. You can write a unit test which asserts that given
replaceCamelWithSpaces('MidnightBlue') will output 'Midnight Blue'. This type of test is low level and validates that the function should replace the camel case with a space. Sometimes the distinction between unit and integration tests can become somewhat blurred. I discuss integration tests next. When testing React components for example, like testing a form, the form component will include other components like the button. Whilst you might think of it as a unit test when testing the form, you are in fact testing other components inside it and it qualifies as an integration test.
An integration test validates that different modules and functions work together to achieve an end result. Like the example that we mentioned previously with a react component form. The form will be made up of other components and testing that form accepts and outputs the correct data is an example of different components and functions working together. A typical form will likely include a text fields, validation functions and button to submit the data. For running unit and integration tests, you need to use a test runner. At the moment one of the most popular test runner is Jest.
End to end tests are like the cherry on a cake. Meaning that they are at a higher level and test how the user will typically interact with your application. This involves running the complete app end to end from the UI to enter log in details, to the API that receives the log in data, to the database that retrieves the profile data for the user logging in and everything else in between. This involves spinning up an actual browser. E2E tests are very useful in catching regressions to previously working features, and they are not concerned with a particular part of the app. There are many tools for creating end to end tests, the two most popular tools at the time of this writing are Cypress and Selenium
This type of test isn't actually a test, but an approach to testing. Meaning the way in which you write tests. So, if we take the example of building the user interface (UI) for an app - you could write a test first that checks we can enter our log in details into a form and validates our email username.
Now the idea behind TDD is that you don't write any code until you've first written the test for that code. This approach has both its pros and cons, and you have many people that advocate to always write tests before writing code. But what's important to understand is that there is no specific time to write tests in the development lifecycle. While writing tests firsts has a lot of advantages, there are times that it's just not feasible to do TDD - like when how to solve a particular problem is not clear.
BDD again isn't a test itself, but again an approach to development. BDD informs TDD in what features to write a test for. For example, TDD focuses on writing tests firsts and helping the developer to create the software step by step explicityly writing tests first. Whereas BDD helps the team as whole - developer, test engineer, product owner etc - in determining the features to implement. BDD focuses on interaction between stakeholders, it requires defining the tests (behaviors) in a way that developers and non-developers understand. There are many tools that can be used for BDD, Cucumber JS is a popular option.