Software testing and TDD
"All code is guilty until proven innocent!"
Software testing is as important as development. Testing ensures that software meets real-world requirements, and tested software ensures reliability, security, and high performance, leading to time savings, customer satisfaction, and cost efficiencies.
There have been various methodologies and tools for testing for a long time and they are highly recommended, but most developers still find it cumbersome and annoying. Testing and QA is a process that requires a concerted effort from developers as well as QA, and developers are encouraged or emphasized to write unit tests, integration tests along with writing code.
There are several methodologies to process and adhere to testing, one of the most famous and widely used is ‘Test Driven Development (TDD)’ or ‘Test Driven Development’.
TDD stands for ‘Test Driven Development’ and is a software development methodology in which tests are written and developed before the actual code is written. TDD was introduced as a technique in extreme programming and is credited to Kent Beck, one of the first extreme programmers, with a process presented in the late 1990s.
Even before TDD, testing practices were already commonplace in development. What makes TDD new and innovative, however, is that testing is usually done by writing code and then writing tests afterward, whereas the TDD approach is to write tests first, before writing code.
TDD is said to have started with a small question.
"If I test the code I write, I get better quality code. What would happen if I look the process to. he extreme: writing tests before the code itself?"
TDD has two different goals, both design and technical.
- Design perspective: It aims for the specification of functionality, not verification.
- Technical perspective: It aims for clean code that works (no duplication, clear code).
TDD is a development methodology that emerged to compensate for problems with traditional processes and to address the above two goals.
- Write failed test code before writing production code.
If a test fails or doesn’t compile as written, don’t write more tests.
- write only the minimum amount of code necessary to pass the currently failed test.
If you follow the above methodology, you’ll notice that you write only one test at a time, implement the code, and complete the code in a very short iteration cycle.
These three rules are called nano-cycles, and they result in iterative test cycles of seconds. According to Kent Beck, the founder of TDD, and Robert C. Martin, author of the Clean Code book, any development style that deviates from the three rules of the nano-cycle is taboo.
- write a test that fails [Red]
- write code to make the test pass [Green]
- refactor the code that passes [Refactor]
The philosophy of the method is based on the idea that developers can’t pursue two goals in development at the same time.
- Correct functional behavior
- Correct design structure
The RGR development cycle forces you to focus on developing software that works first, and then on providing a structure that allows working software to continue to survive. It also helps you avoid unnecessary design by more clearly defining what is expected of the actual code, and drives you toward minimal, concise code.
"Make it work. Make it right. Make it fast" - Kent Beck
Kent goes on to say
"Write a test, make it run, make it right. To make it run, one is allowed to violate principles of good design. Making it right means to refactor it." - KentBeck.
Taken together, Kent's quote suggests that it's okay to go through rapid pre-development to get from Red to Green, and to ignore issues like redundant code or structural weaknesses during rapid development. He also suggests that all design issues are addressed in the final Refactor phase. By working in iterations of micro-cycles, you'll be developing and refactoring code in parallel.
"As the tests get more specific, the code gets more generic." - Robert C. Martin
As the number of tests grows, the tests become more specific. By more specific, I mean a more detailed specification of the behavior. Good developers define functional specifications by making their code more generic. This results in more and more functionality that can be reused universally, rather than code to accomplish a specific goal.
The last basic cycle in TDD is the one to ensure that all the cycles running before it are going to a ‘Clean Architecture’. As you go through the nano and micro cycles, it becomes difficult to see the big picture of the system. By having a basic cycle every hour, we make sure that the overall structure of the system is going in the right direction.
The micro-cycle of TDD (RGR cycle) consists of unit testing, which is the key to realizing the "Clean code that works" that TDD aims for.
Some people mistakenly think of TDD as a testing technique, but it’s really a software development theory. The essence of TDD is not a process for writing unit tests, but a powerful way to achieve ‘Clean code that works’ through the RGR cycle, and through the process of refactoring and passing the tests written, the software can gradually achieve the ideal design that works.
Fast - Unit tests should be fast.
Each unit test should be fast. If tests are slow to execute, developers will feel pressured to run the TDD cycle repeatedly and will not adhere to it, and the overall development speed of the project will be affected.
Independent - They must be independent.
There shouldn’t be any order or test data dependency between each unit test. For example, there shouldn’t be any assumption that the test that creates a user should run before the test that allows the user to log in. If there are dependencies between tests, then a failure of one test will lead to a failure of all tests, making it difficult to determine the cause of the failure.
Rrepeatable - It should always be possible to run a test repeatedly.
Each unit test should always be able to be run again and again, for example, the second test should not fail due to data written in the first test. Tests should also be designed to work in any environment - they should be designed to test only the logic of the code written, not external factors such as the Internet or the DB.
SSelf-Validating - The test should be self-validating.
It should be automated so that the test itself is the final judge of pass/fail. It should be written so that the developer does not arbitrarily define pass/fail manually by looking at the test’s output or generated data.
Timely - should be written before any actual code is written. This is an essential rule in order to proceed with TDD.
The normal way of doing development looks like this: requirements design -> development -> testing.
- Poor ability to detect your own bugs
- Poor quality of source code
- Increased cost of self-testing
Software is constantly evolving, and adding new features and refactoring is an ongoing process. In any project, it’s hard to design everything perfectly in the initial design. Requirements will continue to change and be added, and the software will continue to evolve to meet them as you change the design.
During the redesign process, unused code or redundant features are left behind, and the "code smell" gets worse and worse. As a result, it becomes less and less scalable, and the code becomes complex and difficult to manage and maintain. Code in this state has no predictable ‘side effects’, so everything has to be tested for even small modifications, resulting in increasingly expensive testing and technical debt. Eventually, there tends to be an unwillingness to fix even bad code when it comes to legacy code, and a failure to fix structural flaws.
- write tests before the actual implemented code
- run the tests
- write the actual code
- re-run the test, verify it passes the test
Continuing with the above process will naturally result in less bugs in the code, and it will also make it simpler to reflect and verify features or bug fixes added during the development cycle. It also makes it easier to add features and refactor, as the software requirements all exist as tests and can be tested for functionality.
"Test-driven development (TDD) is a way of managing fear during programming." - Kent Beck
TDD helps developers manage the fear of making changes to their code. Side effects of changes can be verified by tests, making it easier to be more aggressive in refactoring. Tests written and managed in advance of development also allow developers to more clearly define and manage specifications and arrive at a concise, redundancy-free design.
TDD is a powerful software design tool that enables ‘Clean code that works’ and provides a ‘Clean architect’ and fearless refactoring process for modern projects that require rapid requirements and design changes. TDD enables higher quality, faster feature addition and better maintainability of code.
"Software that has adopted TDD has seen an increase in development time of about 15 to 35 percent and a decrease in defect rate (bugs) of about 40 to 90 percent." - Microsoft, IBM.
*This content is a work protected by copyright law and is copyrighted by Elice.
*The content is prohibited from secondary processing and commercial utilization without prior consent.