Behavior driven development
From Wikipedia, the free encyclopedia
In computer science Behavior Driven Development (or BDD) is a programming technique that questions the behavior of an application before and during the development process. By asking questions such as "What should this application do?" or "What should this part do?" developers can identify gaps in their understanding of the problem domain and talk to their peers or domain experts to find the answers. By focusing on the behavior of applications, developers try to create a common language that's shared by all stakeholders: management, users, developers, project management and domain experts.
The purpose is to question each part of the application and the application entirely. These questions deal not merely with technical or requirements issues but also with cost and time related issues. Is it feasible for our organization to build an application of this scope? is a question that can be answered by writing behavior tests. By exposing the complexities early on, developers and management can make better estimations on how fit the organization is to handle the creation of an application.
To prove the behavior of an application during and after the development process application code is tested through behavioral tests. These tests should answer the questions organizations have about the application and illustrate how an application works. When writing behavioral tests developers are assumed to solve the most important or critical questions first. Each test that is written deals with the next most important question on the list. If every question is solved the behavior of the application is defined in tests and the application has been created.
Contents |
[edit] Behavioral tests
The examples below demonstrate how behavioral tests can be written with the Java programming language using the JUnit automated testing framework. This allows the tests to be executed swiftly and constantly during the development process.
The test below verifies if a prime number calculator can calculate the first prime number after 100 correctly.
public class PrimeNumberCalculatorTests extends junit.framework.TestCase { public void testIfPrimeAfter100Is101() { PrimeCalculator calculator = new EratosthenesPrimesCalculator(100); int result = calculator.nextPrime(); assertEquals("First prime after 100 should be 101 but is " + result, 101, result); } }
The test above should be expanded so that more prime numbers are tested, as shown below:
public class PrimeNumberCalculatorTests extends junit.framework.TestCase { public void testIfPrimeAfter100Is101() { PrimeCalculator calculator = new EratosthenesPrimesCalculator(100); int result = calculator.nextPrime(); assertEquals("First prime after 100 should be 101 but is " + result, 101, result); } public void testIfFirstPrimeIs2() { PrimeCalculator calculator = new EratosthenesPrimesCalculator(); int result = calculator.nextPrime(); assertEquals("First prime should be 2 but is " + result, 2, result); } public void testIfPrimeAfter683Is691() { PrimeCalculator calculator = new EratosthenesPrimesCalculator(683); int result = calculator.nextPrime(); assertEquals("First prime after 683 should be 691 but is " + result, 691, result); } public void testFirst10Primes() { PrimeCalculator calculator = new EratosthenesPrimesCalculator(); int[] primes = new int[] { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31 }; for (int i = 0; i < primes.length; i++) { int result = calculator.nextPrime(); assertEquals("Expected prime: [" + primes[i] + "], got: [" + result "]", primes[i], result); } } }
Behavioral tests use the techniques of Test-Driven Development but have a more specific goal. The purpose is to define the behavior of an application rather than the implementation. In the example above it's not really important how the prime numbers are calculated - if calculated at all. What's important is that the numbers are correct which is the expected behavior of the application.
The application users may also require the prime number calculation must perform within certain benchmarks. If this is the case the behavioral tests should record the performance characterics and compare them with the expected results. But again this does not dictate how the prime number calculation should be implemented, it only dictates how it should behave.
[edit] Test conduct
When writing behavioral tests there are a number of practical rules to keep in mind that will help developers to stay on track with the goals of testing behavior instead of implementation. These rules aim to make both tests and application code and design as flexible and robust as possible.
- Test size: each test (that is each test method) should not be longer than 15 lines of code (not counting empty lines). Test methods that are longer are either too complex (or the implementation is inefficient) or test too much behavior. Small tests are more expressive - it's easier to understand the behavior that is tested - and are easier to maintain and refactor.
- Mock objects: the usage of mock objects is encouraged (e.g. by using the EasyMock framework for Java) to achieve the previous goal. Mock frameworks create easy to use - and verify - objects that make tests more accurate, shorter and easier to maintain since the framework creates the implementation of the objects, not the developers that write the behavioral tests.
- Helper methods: some behavior is best implemented in a helper method (e.g. load this or that from the database) that can be easily tested and called from elsewhere in the application. These methods help to make the code more expressive (through expressive method and argument names) and easier to test.
- No dependencies: behavioral tests should not depend on other test cases, the execution order of tests or test cases or the presences of any configured system. This assures that only a limited, well-defined behavior is tested in a fine-grained manner. The opposite is to test fully configured application (components) in a coarse-grained matter. This aspect of these an application is important yet it is the domain of Integration tests.
- Isolated: behavioral tests should work in full isolation to ensure only a very limited part of the entire behavior of an application is tested. The more fine-grained behavioral tests are the better the behavior is tested.
Specifically for Java there are two more rules:
- Packages: tests should sit in a separate source tree but in the same package to allow for better testing.
- Protected methods: helper methods that implemented behavior that can be tested should be declared with package-protected access to allow their invocation from test cases.
[edit] Design conduct
When strictly following the above testing rules applications will automatically adapt a fine-grained, expressive design if combined with some interesting Design Patterns for domain models. This is the goal of Behavior Driven Development, to drive the application design by following behavioral testing rules and design patterns. The Java code example below demonstrates this effect:
public interface IntegerSpecification { boolean isSatisfiedBy(int integer); } public class IsPrimeNumberSpecification implements IntegerSpecification { public boolean isSatisfiedBy(int integer) { PrimeCalculator calculator = new XXXPrimeCalculator(integer - 1); return integer == calculator.nextPrime(); } } public class IsPositiveNumberSpecification implements IntegerSpecification { public boolean isSatisfiedBy(int integer) { return integer >= 0; } } public class IntegerCondition { private IntegerSpecification specification; public IntegerCondition(IntegerSpecification specification) { this.specification = specification; } pubic boolean is(int integer) { return this.specification.isSatisfiedBy(integer); } public boolean isNot(int integer) { return !is(integer); } public IntegerCondition and(final IntegerCondition condition) { IntegerSpecification specification = new IntegerSpecification() { public boolean isSatisfiedBy(int integer) { return IntegerCondition.this.is(integer) && condition.is(integer); } }; return new IntegerCondition(specification); } }
The classes above separate number logic - the IntegerSpecification
interface - from boolean logic - the IntegerCondition
class - and allow for the following Java statements:
IntegerCondition primeNumber = new IntegerCondition(new IsPrimeNumberSpecification()); IntegerCondition positivePrimeNumber = primeNumber.and(new IntegerCondition(new IsPositiveNumberSpecification())); if (positivePrimeNumber.is(2)) { ... }
The behavior of the above classes can be easily tested yet together they can create complex and dynamic mathematical functions.
[edit] Test first, implement next
As with most agile development techniques Behavior Driven Development promotes a short test/implement/improve cycle. Testing before implementing is a crucial aspect of Behavior Driven Development.
[edit] Test
The first step is to write a behavioral test, as shown below:
public class PrimeNumberCalculatorTests extends junit.framework.TestCase { public void testIfFirstPrimeIs2() { PrimeCalculator calculator = new EratosthenesPrimesCalculator(); int result = calculator.nextPrime(); assertSame("First prime should be 2 but is " + result, 2, result); } }
After writing the test the project should compile correctly. This means the PrimeCalculator
and EratosthenesPrimesCalculator
classes should exist and should have a nextPrime()
method. Integrated Development Environment (IDE) tools can help with this by creating those classes and methods that are missing in the project. The code below is generated by an IDE:
public interface PrimeCalculator { int nextPrime(); } public class EratosthenesPrimesCalculator implements PrimeCalculator { public int nextPrime() { throw new UnsupportedOperationException(); } }
In the minds of the developers there may be methods missing from the classes above but since the behavioral test does not define other behaviors this is acceptable.
[edit] Implement
Once the behavioral test compiles, the classes that are tested may be implemented (preferably before other tests are written). Their implementation is finished when the behavioral tests work without failing. The behavioral tests are fixtures that can be used as bearings for developers to know when the implementation behaves as expected.
[edit] Improve
When the behavioral test works correctly, developers may improve the implementation to work more efficiently. This can happen right after the implementation or at a later time.
[edit] Setting behavioral priorities
Developers, designers and project managers should talk with domain experts to determine what behaviors are the most crucial for the application to work properly and be useful for the organization. For each behavior it should be determined - or questioned - if sufficient information is available so that non-domain experts - developers, designers, project managers - can test and implement it. It may not be apparent information is missing until testing starts or even later.