History: This article first appeared in Better Software magazine in March 2006. It was translated into several languages.Once I ran into a problem. Teaching developers how to practice agile programming, such as TDD in various projects, I have often met with confusion and confusion. They wanted to know where to start, what to test and what not to test, how to test a lot at a time, how to call tests and how to understand why tests fall.
The more I used TDD, the more I realized that I was not so much honing my skills, reaching new heights as the fact that it was a movement in the blind. I remember how the thought came to me more often: “Eh, I wish someone would say this before!” Than the thought: “Great, the road is clear”. I decided that I needed to find a way to teach TDD, which shows how to work with it correctly right away and without errors.
')
And this way is programming through behavior. It has grown out of agile practices and is designed to make them more accessible and more effective for teams unfamiliar with them. Over time, BDD began to include agile analysis and automatic acceptance testing.
Express the names of tests (methods) with sentences
My discovery, my joyful “Aha!” I felt when I was shown the deceptively simple
agiledox utility written by my colleague, Chris Stevenson. She takes a class with JUnit tests and prints their names in the form of simple sentences. So the test case looked like:
public class CustomerLookupTest extends TestCase { testFindsCustomerById() { ... } testFailsForDuplicateCustomers() { ... } ... }
printed like this:
CustomerLookup
- finds customer by id
- fails for duplicate customers
- ...
(Note: “CustomerLookup [customer search]: finds customer by ID; drops if customers repeat”)The word "test" is removed from the class name and from the methods, and the camel record of names is converted to plain text. That's all she does, but the effect is astounding.
The developers realized that in this way they could create at least part of the documentation for themselves and they began to do this, writing down the names of the tests in the form of sentences. Moreover, they found that when they wrote down the names of the methods using the business domain vocabulary, the documentation created was understandable to business users, analysts, and testers.
Make no mistake, use this simple test pattern for tests.
Then I found an interesting sentence pattern, according to which all test names should begin with the word
will be (
approx. Should). This sentence -
This class should do something (
note this class
will do something) - assumes that you can only write a test for the current class. So you can not go wrong. If you find that you are trying to write a test that cannot be expressed so, then, apparently, this is the behavior of another object.
For example, once, I write a class that checks input from the screen. Most of the fields are the usual customer details: first name, last name, and more; but then there was a field for the date of birth and another for the age. I started writing `` ClientDetailsValidatorTest` (
approx. Client details validator) with methods such as `testShouldFailForMissingSurname` (
approx. The test will fall if there is no last name) and` testShouldFailForMissingTitle` (
approx. The test will fall if there is no title).
Then I was confused with the calculation of age and mired in a world of confusing business rules: What if there is a date of birth and age and they do not match? What if birthday is today? What is my age, if I have only my date of birth? I started writing long test names to describe this behavior, but I stopped and decided to move it all. So I created a class called AgeCalculator (
note of age calculator) with my AgeCalculatorTest (
note of test for age calculator). So all the behavior of the object for calculating the age moved to this calculator and then `ClientDetailsValidator` needed only one related test, - checking the correct interaction with the age calculator.
If a class does something other than one, then this is usually a sign that I have to create more classes for that other. I create a new service in the form of an interface that describes
what it does and pass it to the first class constructor:
public class ClientDetailsValidator { private final AgeCalculator ageCalc; public ClientDetailsValidator(AgeCalculator ageCalc) { this.ageCalc = ageCalc; } }
This style of creating objects together, known as
dependency injection , is especially useful with mock objects.
Name the tests clearly: help when they fall
After some time, I found that by changing the code and breaking the tests, I could clearly understand the intended behavior of the code by their names. Usually, one of three things happened:
- I wrote an error code. I'm bad. Solution: fix the code.
- the behavior was important, but it was moved somewhere. Solution: move the test and possibly change it.
- The behavior has ceased to be true: the tasks of the system have changed. Solution: remove this test .
The latter often occurs in agile projects, along with an increase in understanding. Unfortunately, TDD newbies have an innate fear of dropping tests, as if this would somehow reduce the quality of the code.
A less distinguishable shade of a word
will (
comp. Should) become clear when comparing it with a more formal alternative
should (
approx. Will or shall).
It will imply that you can doubt the behavior being tested: “Will it really do this?” This makes it easier to distinguish the situation when the test really drops due to a mistake made in the code from the one when your ideas about the system behavior are already wrong.
Use the word "behavior" rather than "test"
So now I had that tool — agiledox — in order to remove the word “test” and use that sentence for each test name. Then I realized that people studying TDD almost always stumble over the word "test".
Of course, it is not true that testing is not inherent in TDD: the final set of methods is a good way to verify that the code works. However, if the tests do not fully describe the behavior of your system, then they are deceiving you with a false sense of security.
I began to use the word “behavior” instead of the word “test” when I was working with TDD, and found that it appeared not only suitable, but also all the questions of the students magically fell away. Now I had the answers to some of those TDD questions. How is it easier to call your test? - this sentence describes the following behavior interesting to you. Question how to test in detail? become purely theoretical: you can only describe as much behavior as the sentence allows. What to do when the test falls? - just follow the steps described above: either you made a bug, or the behavior moved, or the test is no longer needed.
I found that thinking by behaviors rather than tests was so beneficial that I started calling TDD testing through behavior or BDD.
JBehave focuses on behavior, not testing.
By the end of 2003, I decided that it was time to invest money — or at least my time — in what I was talking about. I started writing what should have replaced JUnit - JBehave. There was no reference to testing, he replaced it with a vocabulary built around the behavior being tested. All this in order to find out how such a framework will evolve if I strictly adhere to my new mantra about testing through behavior. I also thought that this tool would be useful for learning TDD and BDD without distractions to words derived from the word test.
To define the behavior for the hypothetical `CustomerLookup` (
approx. Customer search) class, I would write a behavior class called, for example,` CustomerLookupBehavior` (
approx. Customer search behavior). It would contain methods that would begin with the word "will" (
comment should). The program that starts the behavior check (
note the behavior runner) would then create this behavior class and call each of the methods that describe the behavior in turn, just as JUnit does for tests. She would then have to report on the progress in the course of execution and give the total at the end.
My first goal was to have JBehave check for himself. I added behavior that allowed this program to launch itself. I managed to transfer all JUnit tests to JBehave behavior and get the same feedback as with JUnit.
Identify the next most important behavior.
Shortly after these experiments with JBehave, I began to understand the concept of business relevance. Of course, I always knew that I was writing code for something, but I never thought about the significance of the code I was writing now. My colleague, business analyst Chris Matts, pushed me to reflect on business relevance in the context of testing through behavior.
Having this goal — to make JBehave self-verifying — I found that in order to focus more easily, you need to ask yourself:
“What is the next most important thing that the system does not do?”This question will require you to determine the significance of those features that you have not yet implemented and prioritize them. It also helps you formulate a name for the method describing the behavior: the system does not do X (where X is some clear behavior), and X is important; which means the system will do X, so your next behavior is:
public void shouldDoX() {
Now I have an answer to that TDD question, namely where to start.
Requirements are also behavior
So I had in my hands a framework that helped me understand and, more importantly, explain how TDD works and the approach that saved me from all those pitfalls that I discovered.
Toward the end of 2004, I somehow told me a behavior-based approach open to me, Matts, and he said: "But this is exactly as analysis." And after a pause to think about it, we decided to use all this behavior-based thinking to determine the requirements. So, if we could develop convenient vocabulary for analysts, testers, developers, and for business, we would be on the right path to eliminate the uncertainty and misunderstandings that arise when technicians talk to people from business.
BDD provides “accessible to all language” for analysis
Somewhere around this time, Eric Evans published his best-selling book, Domain-Driven Design (
note Domain-Driven Design by Eric Evans). In it, he describes the concept of system modeling, using a language that is accessible to all, based on a business model, so that business vocabulary penetrates right into the code.
Chris and I realized that we were trying to determine the language accessible to everyone for
the analysis process itself ! We had a good start. In general, our company already had a template for user stories, which looked like this:
As a [X]
I want [Y]
so that [Z]
(
note. Being X, I want Y, so what happens Z.)
where Y is some kind of feature, Z is the benefit or value of this feature and X is the person (or role) who benefits. The advantage of this sentence is that it forces you to determine the value of the story being developed at the time it is first determined. After all, it happens that when there is no real business meaning of history, then some degradation occurs to something like this: "... I want [some feature]], so therefore [I will just do it, and that's all, okay?]. " Our method allows us to move beyond these rather esoteric requirements.
And from this start, we with Matts were on the path of discovery, the fact that every agile tester knows: behavior in user history is just a criterion of acceptance, namely, if the system fulfills all the criteria of acceptance, then its behavior is correct; if not, then no. Therefore, we created a sentence template to record the user history evaluation criteria.
This template should have been so simple that the analyst would not feel the limitations and unnaturalness, but also so ordered that it would be possible to divide the history into component fragments and automate them. Therefore, we described the acceptance criterion using the notion of a
scenario that took the following form:
Having ( approx. Given - given) some context,
When ( approx. When ) an event occurs,
Then ( approx. Then) check the result.
To demonstrate this, let's use the classic example of an ATM. One of the story cards would look like this:
+ Name: Client withdraws cash +
Being a customer
I want to withdraw money from an ATM,
To not wait in line at the bank.
Well, how do we understand that the story is complete? We have several scenarios: there is money in the account; there is no money in the account, but it can be withdrawn within the overdraft; account exceeded overdraft. Of course, there will be other scenarios: the account will be in overdraft with this withdrawal, or the ATM has no money.
Using the Having-When-Template, the first two scripts might look like this:
+ Scenario 1: There is money on the account +
Having an account with money
And a valid card
And ATM cash
When a customer requests cash
Then make sure that the account was written off.
And make sure that cash is issued
And make sure the card is returned.
Notice that using a union to connect several initial conditions (
approx. Given) and results (
approx. Then) makes understanding easier.
+ Scenario 2: Withdrawing exceeds overdraft +
Having an account exceeding the limit
And a valid card
When a customer requests cash
Then verify that the disclaimer is shown.
And make sure no cash is issued
And make sure the card is returned.
Both scenarios are based on the same event and even have several common baselines and results. We can take advantage of this by re-using baseline conditions, events, and results.
Make acceptance criteria executable
The fragments of this scenario — the initial conditions, the event, and the results — are small enough to be programmed. JBehave has an object model that allows you to explicitly relate script fragments to Java classes.
You write a class representing each initial condition (
note given) like this:
public class AccountIsInCredit implements Given { public void setup(World world) { ... } } public class CardIsValid implements Given { public void setup(World world) { ... } }
and one for that event like this:
public class CustomerRequestsCash implements Event { public void occurIn(World world) { ... } }
and so on for the script results. JBehave then links it all together and executes. It creates a “world” that exists somewhere to store your objects, then JBehave transfers it to each initial condition (
note given) in turn so that they can initialize the world to some known state. JBehave, then, asks for an event to “happen” in this world that performs the stated behavior of a particular scenario. And finally, JBehave transfers control to any result we define in a particular story.
Having classes representing each fragment of the script, we can re-use the fragments for other scripts or stories. At first, these fragments are implemented using mock-objects for setting money on the account or setting a validity card - this is how we create the foundation for implementing the behavior. As you implement a specific application, the initial conditions and results change and begin to use the real classes you created, and thus, by the time the script is finished, they become true functional tests from start to finish.
BDD present and future
After a pause, JBehave is being actively developed again. Its core is fairly complete and reliable. The next step is integration with popular Java IDEs such as IntelliJ IDEA and Eclipse.
Dave Astel has been actively promoting BDD lately. His blog and various published articles provoked a flurry of activity. The most noticeable is the
rspec project for creating a BDD framework in the Ruby language. I started working on rbehave, which will be a Ruby JBehave implementation.
My colleagues, after using the BDD technician in various real-world projects, reported that this method is a huge success. The JBehave subroutine for launching stories — the part that tests the acceptance criterion — is being actively developed.
In the future, we want to have a full circle editor that would allow business analysts and testers to write stories in a regular text editor that would create stub objects for classes of behavior, all in the business model language. BDD has evolved with the help of many people and I express my deep gratitude to them.
Note Dan North is a teacher of agile development methodologies. Develops software and teaches this for about 20 years. The creator of his own agency for consulting and software development. Introduced the concept of development through behavior (BDD).