What is Test Driven Development (TDD)
What is TDD? The aim of test-driven development is to create clean, simple code that satisfies the requirements with no or minimal code bloat.
How Functionize stimulates TDD
The world of software development has advanced beyond recognition since the days of the waterfall development cycle. Nowadays, developer teams can choose between a plethora of different development methodologies, like Test Driven Development.
Some of these, like Agile, have even become so well-known that managers in other industries have adopted them. These new methodologies tend to lie on a spectrum. At one end you have the Waterfall Model and related methodologies. Here, projects are planned in detail before they are started, development cycles are often measured in months and software is released monolithically at wide intervals. At the other end of the spectrum lies Extreme Programming and Test-driven Development. Both of these use extremely short development cycles and aim to make the developers ultra-responsive to customer requirements. Between these extremes lie other methodologies Kanban, Scrum, and Rapid Prototyping.
Waterfall Still has a Place
All these methodologies have their place. The Waterfall Model can still be applied to really large projects where it is vital to have clear goals. The more responsive methodologies are particularly suitable where customer requirements may not be clear before you start. This is especially the case when you are developing something disruptive when user interactions with early prototypes can determine the direction the product will go in.
In this blog, we are going to focus on Test-driven Development (TDD). This approach is 15 years old but has only recently started to gain wide traction in the software industry. We will look at what TDD is, how TDD can help you as a developer and then will show how Functionize can make TDD easy for you.
What is Test-driven Development?
The aim of TDD is to create clean, simple code that satisfies the requirements with no or minimal code bloat.
Test-driven Development was first formalized by Kent Beck in 2003. As the name suggests, it achieves this by coding to pass tests, rather than to meet requirements directly. The tests are formulated such that they result in the requirements being met. The aim is to keep the development cycle as short as possible so that you are free to respond to changing requirements dynamically and effectively.
The TDD cycle starts with creating a new test (or tests). These should advance incrementally from the existing tests. At the start of the project, a test may be as simple as getting your API server to send you “Hello World”. But as the project advances, tests become more complex and slowly add more functionality to the code. The test should be defined as a proper formal test case, defining the expected behavior and including the expected mode of failure. This may require the use of user stories and the like to explain the required functional behavior.
Second Stage of TDD
The second stage of the cycle is to run all tests, both the newly written ones and the existing ones. If you are doing things right, the new tests should fail in the expected fashion. This gives you confidence that your test methodology and test harness are working.
Next, the developer writes code with the specific aim of enabling that test to pass. The aim here is not to produce perfect code, simply to make the test pass as quickly as possible. The developer is likely to repeatedly test to see whether the new code passes all the tests. Once it does, then the developer can move to the final stage.
Once all current tests have passed, the final thing is to refactor the code and clean it up. As mentioned above, the code that was written to help the tests pass may be pretty grungy. At this stage, it’s important to remove any duplication, apply good coding style rules, add documentation and try when possible to make class and object names self-documenting/self-descriptive. Throughout this process, it’s essential to keep checking that your tests all pass still. Finally, you are ready to start the cycle again.
What makes TDD so popular?
Test-driven Development is becoming increasingly popular for several reasons. One of the biggest is that it provides a rigorous and effective methodology for extremely short development cycles (measured in days).
Often when developer teams try to adopt other hyper-agile methodologies like XP (Extreme Programming), they find that it can be hard to maintain focus on achieving the end result you want. Instead, they tend to focus on producing as much code as they can as quickly as they can. Down the line, this leads to the need for massive refactoring. As an example, one project I worked on a few years ago was running XP with 1-week sprints. About 3 months into the project one of the engineers announced that he was having to refactor so much code that his contribution to the project that week was to delete 70,000 lines of code.
Additional Benefits of TDD
Another key benefit of TDD is that because testing and development happen hand-in-glove, there is no longer a risk of a mismatch in expectations between your QA engineers and developers. Developers have to focus on making their code easy to test, and QA engineers can focus on working with the developers to create the right test cases. This close collaboration leads to much more solid and effective code. Indeed, anecdotally, teams that use TDD tend to find they never need to resort to debugging because all the bugs emerge during the actual development process.
How can you get the most out of TDD?
Like all development methodologies, test-driven development needs you to follow certain guidelines to get the most out of it. Here we give you a brief overview of some of the important things to consider.
Limit the scope. The most important thing with TDD is to strictly limit the scope of each development cycle. Ideally, each cycle should concentrate on a single function or class, or on a small set of closely related functions. If you try to make the scope too large, you end up adding too many new test cases at once, leading to a reduction in efficiency.
Clear test criteria. Tests must be well structured with clear pass/fail criteria. This is really important because you need to have confidence that if the test has passed, the function is definitely working.
Use a reliable test harness. You need to ensure your test harness/framework is completely reliable. Any test failure should demonstrate a failure in the code, not in the test harness.
Maintain your existing tests. Related to the above point, you need to be sure that your tests are always maintained. When you change your code you may risk causing previous tests to fail, even if the actual code is still OK.
It’s also really important to think about how to structure large projects such that they can be managed using TDD. One way to do this is to seek to modularise your product so that each team can develop their part of the product in relative isolation. It is also helpful to treat things like test maintenance as a major part of the project, with sufficient developer/engineering resources to ensure that you keep on top of things as the project grows.
How can Functionize help you with TDD?
As we have seen, critical to the success of TDD is creating good tests that are robust, reliable and maintainable. Tests created in Functionize automatically meet these criteria.
Functionize leverages machine learning and other forms of artificial intelligence to create tests that are able to self-heal, to provide easy tracing of the root cause of test failures and to generate tests from cases written in plain English. Furthermore, the system is able to test at scale across and is completely agnostic to changes in things like web frameworks and libraries. So let’s look at a couple of these things in more detail and see why they can help with TDD.
Functionize’s ML Engine is designed to ensure that tests are able to self-heal. This is critical in any automated testing environment where simple changes like moving a button on the screen or simply restyling a page can trigger false test failures. ML Engine applies advanced machine learning techniques to allow it to intelligently diagnose and solve test failures. Root cause analysis allows it to identify the likely cause of a test failure, even if it happened several steps earlier it does this using a combination of test history, machine learning, and a rules-driven expert system. The system is then able to make smart suggestions for how to fix the problem. It will even test all the solutions to check which one is correct. Finally, self-healing allows you to accept the best suggestion with a single mouse click.
This self-healing is critical for making TDD efficient. With most automated test frameworks, test maintenance can end up consuming a significant share of the team’s time. When creating quality tests is core to the whole development process, any time you can save in test maintenance is going to improve productivity significantly.
Automatic test case generation
One of our newest innovations is NLP Engine. This allows you to write tests in plain English and then uses Natural Language Processing to convert these into fully functional tests. This means that everyone on the team can now contribute not just to defining tests, but also to creating them. The test case is written out as a series of steps which are then taken by the system, parsed, interpreted and converted into the actual test. The test can then be run just like any other Functionize test.
For TDD, this is invaluable, since it allows you to define tests before you actually have the code written. This is an issue that would prevent many automated test frameworks from being used for TDD where, by definition, when the test is created there is no actual code yet to test. Of course, as projects develop, tests often incrementally advance from previous tests. But even here, the ability for non-experts to define tests will give huge gains in efficiency and productivity.
TDD can be a really effective development methodology. Because TDD encourages developers to focus on writing code that achieves the specific required functionality, it leads to less bloated code. It also tends to result in stable code that needs little refactoring. Functionize’s artificial intelligence-driven test framework is particularly useful for TDD because it simplifies both test creation and test maintenance. As a result, it allows the team to focus on defining good tests and developing good code.