Should we use TDD, BDD… or both?
Test-Driven Development and Behavior-Driven Development, which one is better?
You must have heard a lot about these two, TDD and BDD. But which software development process is the best, and why is it better than the other?
The answer might surprise you, but do read on.
What is TDD?
Test Driven Development (TDD) is a process by which tests are written before the code. In TDD, a test for a small part of the code is referred to as a unit test, and writing this test will precede the coding of that part of the program. The process will repeat until every unit of the program is covered and passes its tests. Writing unit tests for new code is much easier than it is for existing code, however that does not mean that it is easy.
The key challenge in TDD is having the units tested in isolation. Isolation means writing the test that excludes any actual dependency of the tested code and having the test scope only cover the unit’s methods or classes.
What are the advantages of having the test isolated?
By running isolated unit tests, developers will be able to spot and fix any defect in their program faster and more easily. When a defect is discovered by a failing test, there will only be a small area of code to inspect and debug.
If a unit test is done with no isolation, meaning it will use actual run-time classes and components for the code dependencies, the test results may be unpredictable. For instance, a unit test that needs to read from an external database, and expects certain value from the database, may fail if the external database is changed. The integrity of the test will be questioned each time it runs because the same unit test could either fail and succeed depending on the value provided by the database. External dependencies of the unit test such as a database will cause unpredictability and inconsistency in the result, making it harder to track down the defect in the code. The speed of the unit test will also be slower due to the latency of the interaction between the test and the external sources it depends on. When a developer has to run hundreds of tests, low testing speeds are a big problem.
Isolation sounds good, but how do I isolate my unit tests?
One way to isolate a unit test is by providing mocks, which are fake objects that can mimic the actual dependencies. Running the test by replacing external sources with quick mocks will usually improve the speed of the test significantly. Mocks can be written such that they will return some expected results every time, and can even enforce validation rules or count the number of times the methods in the tested unit were called.
However writing mocks for unit testing can be tedious and time consuming. There is an option to shorten the time spent for testing by using mocking frameworks. Frameworks like this usually provide an interface to create mocked objects which return canned responses or throw exceptions based on a pre-defined set of parameters. Mocking frameworks are essential in the TDD process. Creating our own mocking framework is possible, but it may delay our development and unnecessarily exhaust our resources (time and manpower).
So, back to TDD. What’s in it then?
Thanks to the small coverage of each unit test, TDD can help developers to efficiently detect flaws in their code, minimizing the time that otherwise could be spent tracking them down through layers of architecture.
Now, what is BDD?
In Behavior Driven Development (BDD), tests are written in plain English describing a set of behaviors as the expected results of a software system. Each test is called a Behavioral Test, and is divided in three sections; Context, Event and Outcomes. Context is the starting state, Event is what the user does and Outcomes are the expected results after the event. Behavioral Tests can be written at any time — before, during or after development — while in TDD, unit tests should always be written before development. In BDD, there is no particular difficulty in writing behavioral tests after development.
That said, ideally in BDD the tests will also be written before the code, especially whenever a new feature is being developed. After code is written, if the test fails (the behavior is not as expected) then the code will be refactored; if it succeeds then the developer can move on to the next feature and run the respective behavioral test on it after. In short, it’s termed Behavior Driven because it emphasizes developing features based on business goals and desired behaviors.
What are the advantages of BDD?
The nature of BDD that uses plain English to describe the expected behavioral outcomes of a system allows non-technical people (business owners, sales, product management) to focus on the behaviors that actually matter for end-users. It promotes more involvement in the product development from more parties in the business.
Behavior Driven Development can also help developers to be more focused on writing code that does just enough to fit in the frame of the expected behaviors and pass the test. It prevents them from writing too much code or code that does unnecessarily too much.
Is there any drawback?
In general, behavioral tests are slower to run than unit tests. Furthermore, when the test fails, it can not indicate the root cause of the problem; only that ‘it does not work the way it is supposed to’.
What are the key differences between TDD and BDD?
In TDD, unit tests are technical by nature, which limits readers and writers to developers or testers. However in BDD, Behavioral Tests are usually dictated by people who understand the customer more, such as product owner or manager. Due to their nature being relatively similar to plain English, Behavioral Tests can be read and evaluated by just about anybody — stakeholders, business owners, product managers, developers and testers.
TDD may also be faster than BDD in that BDD requires more setup and communication across teams prior to writing the tests. Unit testing can make use of mocks as an alternative to the actual dependencies and developers can even save more time by using mocking frameworks. Furthermore, in TDD, developers can detect bugs easily and efficiently; however in BDD Behavioral Tests can report a malfunction in the program but can not reveal the exact part of the code that fails.
When it comes to code coverage, the higher it is, the easier the code maintenance will be in the future. However in unit testing, code coverage is rarely to higher than 90%. In behavioral tests though, the code coverage tends to be higher, even up to 100%.
Due to its high-level nature, behavioral tests are easier to maintain over time, meaning there will be fewer changes to the tests as the code changes; most changes to the code will not affect the high level behavior of the software. On the other hand, unit tests typically need to be changed whenever the code is changed. Unit tests are also specific to the code that they cover; for instance, changes in the programming language or its framework will result to changes to the unit tests. In BDD, behavioral tests will remain the same even though the language in which the program was written is changed completely.
So, which one is better, TDD or BDD?
The best practice is actually to implement both approaches as they actually complement each other nicely. Behavior Driven Development makes use of Behavioral Tests to guide teams to build products that deliver exact value to the customer and prevent unnecessary code in the development. Combined with Test Driven Development, unit testing will also improve the quality of the product because bugs will be spotted and fixed sooner, without waiting for it to blow up later when more layers are already built up in the architecture.