22. November 2024 By Steffen Albrecht
‘Test First’ begins for developers in the refinement phase
Why write tests when the code is already finished? Early testing does not start in code, but in refinement. In this blog post, I'll show you how ‘Example Mapping’ helps to integrate Test-First directly into refinement. This ensures that requirements are clear from the start and enables faster and better feedback.
Why test?
First of all: why test? While the simplest answer, ‘to test whether what we have built actually works’, is not wrong, it does lead to a conflict and a major problem.
If we only test what we have built (I am pointing out the past tense here!), then it is much more practical to create the tests afterwards. So why ‘test first’? But testing retrospectively means that we only test what we have built, and only that. But have we built it right? And if so, have we built the right thing? Such retrospective tests cannot tell us that. So we need a better answer to the question ‘Why are we testing?’
Let's take a look at software engineering practices. David Farley describes them very well in his book ‘Modern Software Engineering’.
- Iterative work
- Incremental work
- Experimental work
- Empirical work
- Obtaining fast, high-quality feedback
Automated tests support all of these points, if they are not even indispensable. The last point in particular, ‘obtaining fast, high-quality feedback’, should be emphasised here. The fastest feedback that most developers are familiar with is the inline compiler of modern IDEs. One wrong character and we immediately know that what we have just created does not work and usually get an immediate hint as to how we can fix the problem. The feedback from automated tests comes later, after you have created the tests. If, on the other hand, you consistently follow the test-first approach, you get feedback much earlier, even before the test has been run, and thus much faster than even the best IDE compiler! The ‘secret’ lies in the test cycle.
It starts with ‘Write a failing test’ and here already lies the key to early feedback. To write a test that doesn't work, we need answers to questions like ‘What do we want to test?’ and ‘What conditions must be met for the test to work?’ If we talk about this with the right people, we will already get the first valuable insights.
Test-First in Refinement
As developers, we are usually the first to be confronted with the latest implementation requests in the refinement phase. At the very least, the scope and criteria that define successful implementation (also known as acceptance criteria (AC)) should be clarified. We discuss this with our colleagues and the stakeholders involved, because these are exactly the right people to talk to. Why not start with Test-First and get some initial feedback? The good thing is that most development teams already do this on an informal level. However, just chatting about it informally is not very professional. Refinement meetings, which develop into excruciatingly long and torturous ‘specification workshops’, often don't lead to better results either and are usually just terribly boring time wasters.
One way out of this dilemma is the ‘Example Mapping’ method. The refinement begins as usual with the presentation of the task to be solved, here called a ‘story’ for the sake of simplicity. Questions are asked and discussed. Questions that cannot be answered are explicitly noted. So the story is not yet ‘finished’. As soon as the comprehension questions have been clarified, the acceptance criteria of the story must be clarified. Each story should have at least one acceptance criterion (AC). Every implementation requires effort. Why should you invest in something whose scope is completely unclear? Therefore, if a story has no AC, it is not ready. As usual, the ACs are noted on the story. But what quality do the ACs have? Something like ‘All payment methods available worldwide are possible’ could be a bit long-winded. Unfortunately, not all AKs are as clearly recognisable as ‘unfavourable’. This is where the actual ‘example mapping’ comes into play. At least one example should be defined for each AK, which can be used to demonstrate the implementation. If no example can be found for an AK, this AK should be deleted or converted into an open question.
The first example should represent the ‘happy path’. If there are several, further examples will quickly follow. Experienced product developers also think of borderline and error situations here. What all examples have in common is that they do not increase the implementation effort. If they do, it is a sign that it is not an example, but an AK.
Wait a minute, you might think that error handling is not an example because it increases the implementation effort. This may be true for prototypes. Functions for productive use without error handling rarely give pleasure.
As a rule, every story can be cut based on its AKs and a story that only contains error handling is not a story but a bug fix!
The ‘Given - When - Then’ format has proven useful for describing the examples, as it is also well understood by ‘non-technicians’. Let's take a look at the AK mentioned at the beginning of this article: ‘All payment methods available worldwide are possible’. Since ‘all’ is not feasible, the AK could be changed to ‘All common payment methods available are possible’. If you start with examples such as ‘Given are VISA payment information (card number, expiry date, card verification number), the purchase is confirmed, the payment is carried out and a confirmation of the successful payment is displayed’, it quickly becomes clear that every further payment option leads to additional work. If you start describing the error handling, it will be a very long refinement. Therefore, you would quickly come to change the AK here to: ‘Payment with VISA is possible’. The relevant examples are now manageable. All other payment options with their specific constraints become their own AKs or their own stories. The clear and simple description of the examples makes it possible to achieve effective refinement with the stakeholders and our product people, with great clarity about what is to be implemented.
Conclusion
Test-First means thinking in terms of tests from the outset. If we find a common language with our stakeholders, for example through example mapping, we get better requirements through rapid feedback. Requirements that are understood from all sides, whose implementation is clear and structured, and where it is defined from the outset when the implementation has been successfully completed. Without code, no compiler is faster.
Would you like to learn more about exciting topics from the world of adesso? Then take a look at our previously published blog posts.
Also interesting: