The Test Series-Introduction
This article contains the TDD approach, how to write clean tests, the FIRST principles, test types, real-life examples like running in the CI/CD pipeline, and more. The following articles will be about the Unit Test and the Integration Test.
Introduction
The test side of the software is an essential topic. There are many types of tests but we will review some of them. Especially the ones that belong to the developers. Let us get to know some concepts and focus on how can understand and write correct tests.
Test Driven Development
We do not use the TDD approach in this series, but I highly recommend you learn what is it. The TDD can be learned through reading Test-Driven Development by Example written by Kent Beck in 2002.
I explained the TDD Cycle as a list. You can also see the TDD Cycle in the following picture.
1- First Step, Red: Write failing tests. Start by writing a test that captures the desired behavior or functionality. This test should initially fail since the corresponding functionality hasn’t been implemented yet.
- Run all tests. Execute the test and observe the failure. This step confirms that the test is correctly assessing the missing functionality.
2- Second Step, Green: Write code for passing tests. Create the simplest possible implementation that can make the failing test pass. This implementation may need to be completed or optimal but should be enough to satisfy the test.
- Run all tests. Execute the entire suite of tests, including the newly written test and any existing tests. This ensures that the changes made in the previous step haven’t broken any existing functionality.
3- Third Step, Refactor/Blue: Refactor code. Improve the code’s design, structure, and performance without changing its behavior. This step focuses on writing clean code; like readability, eliminating duplication, and optimizing the codebase while keeping all tests passing.
- Repeat the cycle. Return to the First Step and repeat the cycle for the next feature. Each iteration builds upon the existing codebase, adding new tests and implementing the corresponding features.
By following the TDD cycle, developers can gradually build a codebase with high test coverage, reliable functionality, and maintainable code.
Test Types
There are two types of tests, Functional Testing and Non-Functional Testing.
- Non-Functional Testing is divided into Performance, Security, Usability, and Compatibility Tests.
- Functional Testing is divided into Unit, Integration, System, and User Acceptance Tests.
Unit & Integration Tests
As you read above, there are many types of tests. Some of these are the responsibility of the developers which are the Unit and Integration Tests in the Functional Testing side. The others also could be in responsibility of developers like Performance Tests, and there are very important and must apply in the projects.
As you can see above the picture, the most important part of the Functional Tests is the Unit Test and the second one is the Integration Test. The Unit Test covers the business of the project and the Integration Test provides to test the integrity of components.
And developers have a question the first time write these; why do developers write the Unit and Integration Tests?
The tests are provided to be sure our application runs correctly and detects possible errors before new features or fixes deploy on the Pre-Production or/and Production environments. We write tests to avoid any surprises. On the other hand, these tests provide keep under control how code snippets or services react to the cases and the effect they have on the existing system.
Just think that LeBlanc’s Law when you put off writing tests:
Later equals Never.
Writing Clean Test
One of the important things is writing tests cleanly as in production code. Also, the principles we use in production code apply to tests as well. There is a long list to write cleaner tests.
- At least one test class per class must be written in the project. The reason for that is the Single Responsibility Principle.
- When writing test cases of a method, we should test each case in separate test methods. The test-case relationship will be explored in depth in the next articles.
- The Build-Operate-Check Pattern is made obvious by the structure of these tests. These three parts split methods as more readable and cleaner. The pattern can be implemented by naming ‘Arrange-Act-Assert’ / ‘When-Given-Then’ or else and provides readability.
- Test Naming Conventions can be applied like adding the ‘Test’ suffix when creating a test class of a class. For example, if the class is called ‘ProductService’, the test class might be called ‘ProductServiceTest’.
- Names of test methods in the test class are usually created by adding the word ‘test’ to the name of the method being tested. For example, if the name of the tested method is ‘add’, the test method name can be ‘testAdd’. We can extend the rest for that could be more readable.
- Test classes are used in a run where software tests are performed and error checking is done. These classes are usually custom developed and do not use normal run times. For this reason, it is generally preferable not to declare the classes and methods of test classes as public, default is used.
FIRST Principles
As I mentioned above, it is important to write clean tests. I had this thought while reading Robert C. Martin’s book Clean Code: A Handbook of Agile Software Craftsmanship. He, who is known as Uncle Bob, talks about the FIRST in his book.
Fast
Tests should run quickly. If that doesn’t happen it will slow you down, you will find problems late and it will affect your development.
Independent/Isolated
The tests should be independent of each other, they should not affect each other. One test should not need another test to run. It has many disadvantages such as possible test errors are difficult to resolve.
Repeatable
The tests should be able to work in any environment and give the same results. For example on your local, in the CI pipeline, or in the Pre-Production environment. If you do not want unexpected a surprise in critical deploys, this rule should be considered.
Self-Validating
There is no need to effort should be made to find out if the tests are working and manual intervention should not be done. For example, the log should not be examined to see if it is working or why it is not working.
Timely/Thorough
Uncle Bob says the tests need to be written before the production code like the TDD. Because TDD makes it easier to write test codes and makes test writing inevitable.But I know, not easy to apply this principle to all projects.
Some of the articles have mentioned Thorough instead of Timely. This indicates that when we want to test a method, we should write tests for all possible scenarios like use cases, and edge cases.
While coming to the end of our article, I would like to end by changing a word from Uncle Bob. Always write tests and keep them clean.
Introduction of Technical Topics
I would like to talk a little about the technical parts of the test subject. Let us get to know two libraries before the Unit Test article. Mockito and JUnit are the most popular libraries in Java Tests. And let us talk about how the tests we wrote are used/automized in real life.
Mockito
Mockito is an open-source mocking framework used as a Java testing Framework and is generally used when writing unit tests. It allows us to mock dependencies, objects, method invoke and etc.
JUnit
JUnit is a unit testing framework for the Java programming language. JUnit provides tools and features to make unit tests easy to run and manage. We will use the JUnit 5 version.
How to automize running tests?
Real-life projects include CI/CD Pipeline generally. I wrote an article about this. The pipeline triggers the tests automatically the way we according. There are a couple of products to check the projects about clean code principles, testing qualities and etc. These products can be usable in the pipeline.
Besides, these products like SonarCloud measure how many lines of code have been tested and projects can have code line/code condition coverage limits. The limit important is certainly important but we should not write tests to pass the limit. We should write tests to cover the edge cases, throwable exceptions, and components or code snippets integration.
Conclusion
You can express your opinions about this article in the comments, and you can follow me so that you do not miss my new articles.
Thanks for reading the end of the article. See you in the next articles. If you have any questions which are related to these topics, please do not hesitate to contact me via LinkedIn.