Software Factory 3 - Software testing: TDD and BDD are in a boat …
Welcome in this third post about Software Factory. I started by introducing the notion in the first post, then I gave you my views about crucial infrastructure questions and also some “hype” keywords like DevOps and NoOps. In this new post, the idea is to cover software testing and testing methodologies.
And again, as I already said, you should never forget that we are in front of a 70/30 problem (if you haven’t read the first article of this serie, you probably should) . Right testing frameworks are only 30% of the topic. 70% of the solution is to work in an environment where people understand what testing software means in terms of time or efforts and why it is crucial to the product quality.
This article will present
2 well-known methodologies to align software testing efforts with business quality requirements, TDD, for Test Driven Development, and BDD, for Behavior Driven Development.
Serious Devs do not need to test their code. Really?
Before talking about TDD or BDD, let me first clarify why testing your code is useful.
Software testing fits into the quality control category. Humans always make mistakes. We need control to fix them.
Software Testing is needed for two main reasons:
- First it allows to make sure that your work is in line with your expectations (specifications). You will be able to point out and fix defects and errors that were made during the development phases
- Second, in a world where your products/services evolve continuously, testing corpus are the way to make sure that new features that you will add in the future will not create any side-effect regression. To achieve this, your tests should be clear and stable in time.
This second aspect can be difficult to achieve in practice. You will need to really clearly define what you need to test.
You will be tempted to test the actual implementation and not the functionality. The result will be that when you add new features, you need to change the code but also to change your tests corpus. In this situation, you cannot make sure that new features won't create regression because you never test with the same perimeter.
To avoid this and help you create a sustainable code, TDD and BDD are software testing methodologies that can help you write good tests.
Test Driven Development, principles, advantages and drawbacks
When we speak about software testing methodology, the first name that comes out is TDD.
TDD principles to develop and test software
TDD is an interactive software development and software testing methodology based on the following process:
- Write a failing test
- Make the test pass
This is the TDD cycle – it’s really simple. It is 3 little stages. Nothing less, nothing more. But between theoretical rules and practice there can be a big gap and a lot of nuances that we will try to cover and explain.
What means “writing a failing test” in TDD style
The idea is to imagine what next step you will need to deliver to have your implementation evolve towards your final goal. Generally, this can be rephrased by “writing the next specification”. Basically, in TDD, your next test is your next specification about how the software should behave. Because you haven’t done that yet, this test is going to fail (but you will fix that latter).
Writing test before its implementation has some key advantages:
- Good tests corpus: You make sure to have tests that cover your features (no test task postponed because there is more urgent task to be achieved, no code written without having ideas about how to test it). If you write test first, you are sure to have at least tests.
Better error handling: When you code, you first generally think only about the “normal” way. In a second time you hack it to add the “error way”, to manage how your code will behave when something does not happens as expected. Change your mindset and think about test before features development generally results into better “beautiful code” with a good error handling path. “Normal” and “error” ways are treated with the same priority level, your code is cleaner and therefore easier to maintain.
Make the tests pass
In the second step of the TDD process, the idea is to make tests pass quickly. The intention is not to design everything perfectly, it just to have tests passing. Your collection of tests will move progressively from failing to all passing and now that all your tests are Ok, it is safe to refactor. The idea is not to have directly the best design. When all your tests are done and passed, you can have a look at them and ask yourself “How do I want to improve this design? What it is missing” and you can then safely improve this design through the refactor phase.
Refactor phase, an iterative phase
Refactor is not a single one-shot step. It is a multi-process in itself.
Idea is: you look at your code and you see what changes you would like to make and you implement those changes. You then make sure that tests are still ok (no regression) and again you ask yourself if there are any other changes that you want to make. You improve your code quality by iterative process and the test suites you already wrote allow you to check for non-regression. Refactor term should be carefully handled as a lot of developers misunderstood it.
When you are “refactoring”, you are not changing the behavior of your code, you never add a new piece of functionality. You refactor to get the codebase into a clean state where you want to be able to add a new feature (by cycling to the first phase of TDD) but by definition refactoring does not alter the externally observable behavior of your code.
That’s that folks! TDD is no more complicated. The main challenge there is about changing culture and habits to think about testing first and not about coding. This change will not happen in one day, it requires an “evangelist guru” who will have enough influence inside your company to change mindsets.
A little rewind to TDD origins
We have spoken about TDD but we have not spoken about TDD origins. Like the Software Factory idea, you will be surprised how old this idea is. It’s been there since the beginning of computer science.
Kent Beck is credited as the TDD inventor but himself claims he has just re-discovered it.
The original description of TDD was in an ancient book about programming. It said you take the input tape, manually type in the output tape you expect, then program until the actual output tape matches the expected output.
After I’d written the first xUnit framework in Smalltalk I remembered reading this and tried it out. That was the origin of TDD for me. When describing TDD to older programmers, I often hear, “Of course. How else could you program?” Therefore, I refer to my role as “rediscovering” TDD
The oldest reference book for TDD comes from 1957:
The first attack on the checkout problem may be made before coding is begun. Inorder to fully ascertain the accuracy of the answers, it is necessary to have a hand-calculated. Check case with which to compare the answers which will later be calculated by the machine. This means that stored program machines are never used for a true one-shot problem. There must always be an element of iteration to make it pay. The hand calculations can be done at any point during programming. Frequently, however, computers are operated by computing experts to prepare the problems as a service for engineers or scientists. In these cases, it is highly desirable that the “customer” prepare the check case, largely because logical errors and misunderstandings between the programmer and customer may be pointed out by such procedure. If the customer is to prepare the test solution is best for him to start well in advance of actual checkout, since for any sizable problem it will take several days or weeks to and calculate the test.
Digital Computer Programming D.D. McCracken, 1957.
It is easy to find a lot of references in the 60’s or 70’s of computer projects that align with the TDD philosophy.
And what about TDD method drawbacks?
I can’t see any disadvantage in the use of a TDD approach in your dailysoftware development.
Its main drawback lies in the mindset it requires. TDD is a huge change in developer habits. Changing one’s mindset is not an easy task and takes time. This transition should not be underestimated.
Another critical point is that tests created during TDD are generally created by the developer who is writing the code being tested. If this method is not mixed with Pair Programming or Code Review practices, it can lead to a bad test coupled with bad code. The bad test will pass, giving a false sense of correctness. This can happen, for example, when the developer misunderstood the specification. Pair programming or Code Review can be adopted to cope with this risk by putting more “eyes” and “brains” into the process.
And finally, TDD is not the alpha and omega of testing methodologies. It mainly produces unit testing. Unit testing are important but other testing techniques should also apply on a project: integration test, robustness test, acceptance test…)
BDD, take behavior into account in your software testing
An evolution based on TDD
Behavior Driven Development can be seen as an evolution of the TDD method. This methodology was presented by Dan North to answer some TDD critics.
Beginners who started with TDD always asked the following questions:
- Why do I need to write tests first?
- Ok I should write tests first, but by which test I should begin with?
- Should my test cover everything or just extreme cases?
This generally leads to no fully clear answer but feedback mainly based on experience and to an intermediate phase where the beginner has understood that it is probably a great method and tries by himself/herself, creating his/her own rules step by step.
- Dev write code and write tests around it.
- At some point, tests and code base growth and dev realizes that test written before code allows to be more focused on code and give a simpler code to maintain.
- When a dev comes back on a code with associated test, test can also be used as documentation to understand what the code should (and should not) do.
- Dev begin to apply TDD.
- Dev understand that TDD is not only testing, but also allows defining the behavior of the code
- Behavior defines interaction between objects and so using Mocks/Stub becomes useful for testing.
- The idea to use a fixed template using a natural language to describe test and to focus on behavior to allow a better team communication. The whole team (and not just devs) will have a better view about what they do, what has already been done and what still needs to be done. More conversations will happen, knowledge (and so expertise) will be more easily shared.
Going to phase 3 is generally quite natural. But going further this point is less easy.
The goal of BDD is to break this step by step scheme by integrating from the beginning that behavior is at the heart of development. Instead of testing for testing, we validate that the behavior of a feature is aligned with the initial contract (specifications).
At the end, you will do TDD, but speaking about behavior has some advantages. First, it is simpler for a beginner to understand and implement and second, speaking about behavior allows to align business expectations with dev work. To achieve this, BDD recommends using a fixed test scenario format.
The “GIVEN, WHEN, THEN” mantra: One language to rule them all (and in the darkness bind them)
BDD forces developers to talk about behavior. We no more use the developer’s vocabulary; we use a pseudo natural language which allows to integrate other people (business, Q&A, marketing, manager) into the development process. Tests case should be understand and discuss by everywhere involve in the project.
BDD software testing method encourages the project team to ask the right questions before writing test and coding. In the end it leads to an efficient synchronization between the specification (use case or user story) and the tests written.
Use case or user stories are focusing on behavior. It is important to understand who needs what and why. BDD takes this principle and reuses it to write test scenario. With BDD method, a testing scenario will follow this template:
Given an initial condition
When some action happens
Then I have the following result
The idea to use a fixed template using a natural language to describe test and to focus on behavior to allow a better team communication. The whole team (and not just devs) will have a better view about what they do, what has already been done and what still needs to be done. More conversations will happen, knowledge (and so expertise) will be more easily shared.
Communication is the key
As a conclusion of this post I would say that the 2 methods for software testing I talked about, Test Driven Development and Behavior Driven Development are bound together.
BDD is not a revolution, it is more like an optimized TDD method. If you need to remember one thing about TDD/BDD, it is that communication is the key. It is important to ask the right questions to the right people (and not only between devs) and make sure that everybody communicates with the same language. To achieve that, this language should be as natural as possible. In the end this has the advantage to better define and write tests, because scenarii will be concrete cases focused on behavior with “real” testing examples.
It’s not a magic recipe and testing is a large and complex topic but applying BDD can help you to bring some meaning into your software testing method and realign business and development objectives.
Dear reader, do not hesitate to comment or ask questions, I will be happy to discuss and challenge my views. In the meantime I will be working on the next article of this Software Factory series. It will focus on integration and deployment.