Blending Practice: BDD & TDD

When starting on a new software product, API services and backend functionality may be provided, and its common to first check that these services are functional. These initial tests are undertaken by hitting the API services and experimenting with both positive and negative case until everything is clear.

This is how Test-Driven Development (TDD) and Behaviour-Driven Development (BDD) can help, and in this article, we’re going to discuss these further.

TDD: Test-Driven Development

What is TDD?

TDD is a process that relies on tests to create a product. We have talked about TDD before in another article. The key is to treat TDD as the first user before we launch the product into the market. Think of TDD as Magician or Stand-up Comedian who tests their material on small group of audience members first to see if it performs well, what mistakes are made, and test different material until everything goes well.

A programmer has spent a lot of time learning one or more programming language, so it shouldn’t be difficult to learn the principles of TDD. In practice, though, it can be quite hard to apply. Another point to consider is that TDD promotes MicroDesign over MacroDesign. TDD might not be mentioned during sprint meetings in Scrum because TDD is not covered in “regular” Scrum. It is, however, part of the Pair Programming paradigm, where two programmers work alongside each other at one workstation. In this mode, one programmer (the “driver”) writes the code, while the other (the “observer” or “navigator”) reviews each line of code as it is entered. Sometimes this pair can combine a programmer and a tester, but in practice the two will switch roles regularly.

In-depth how developers handle TDD in the project 

Let us look at the login feature or login case as an example. Here is the code:

image 1

As seen in this image (image 1), the test coverage is about 93%. Sometimes in TDD, 100% coverage can’t be achieved, but each feature needs to be as fully covered as possible in positive and negative states. In this case, the coverage is not 100% because there are 2 (two) functions on LoginFacade that have not tested, except the loginWithEmail function. After we have refactored the test to call loginWithPhone function, the result is:

image 2

We have reached 100% coverage, but everything is still not covered yet. Both functions performed a single test, loginWithEmail with the positive state and loginWithPhone with the negative state. By simple maths, the tests only covered 2 out of 4 scenarios (or 50%).

Scenarios are at the heart of the TDD process to calculate feasible test results. One question that often pops up, is “What needs to be tested?”. The simple answer is, the product itself. The product could be a document. In Scrum, a document should be clear, simple, and straight to the point. Without proper document or business plan, TDD cannot be undertaken.

A document describing each feature is mandatory. With any feature change, the TDD process will be repeated and the test scenarios may be altered and testing restarted. Coverage targets still need to be achieved. The necessary repeats of the TDD process with every feature change may make the TDD process seem slow but will smooth the end processes as it reduces the time needed by the final testing team before delivery. Another advantage of TDD is that it can capture bugs before being tested by the tester team, and make the product more bug-free.

Let’s get back to our case:

image 3
image 4

After refactored and adding 2 more test cases (for negative and positive), the coverage appears to be 100% (image 3). The login feature seams valid and ready to publish, but in the LoginRepository class there are still yellow highlighted areas (image 4), indicating otherwise.

The yellow highlights, in this case, indicated that the process has not been done yet. This scenario has, therefore, not covered all conditions, and may produce bugs in the future. In this case, the conditions that are still not covered are null email, null password, email and password not null, invalid email, and invalid password. We will need to refactor the LoginRepository process again and re-run the test.

image 5

Image 5 shows that the coverage is now down to 97% and yellow and red highlights have appeared. Yellow indicates that the condition is still not fulfilled, and red indicates that the statement is not called. Refactoring should be repeat, and after several tests of the best scenario test plan, it should be effective. For the scenario itself, it can be created with a json file or map.

image 6

Remove the previous test scope and replace it with a new scope from above to cover all conditions (positive and negative). Create collections of maps for the login feature and do iterations.

private Set<Map<String, Object>> loginWithEmailCases = new HashSet<>(Arrays.asList( 

new HashMap<String, Object>() {{ 

put("name", "Login with email - empty email"); 

put("email", null); 

put("password", ""); 

put("expected", LoginResult.EMPTY_EMAIL); 

}}, 

new HashMap<String, Object>() {{ 

put("name", "Login with email - empty password"); 

put("email", ""); 

put("password", null); 

put("expected", LoginResult.EMPTY_PASSWORD); 

}}, 

new HashMap<String, Object>() {{ 

put("name", "Login with email - login failed 1"); 

put("email", "no@email.com"); 

put("password", "1234"); 

put("expected", LoginResult.LOGIN_FAILED); 

}}, 

new HashMap<String, Object>() {{ 

put("name", "Login with email - login failed 2"); 

put("email", "demo@email.com"); 

put("password", "1234"); 

put("expected", LoginResult.LOGIN_FAILED); 

}}, 

new HashMap<String, Object>() {{ 

put("name", "Login with email - login failed 3"); 

put("email", "no@email.com"); 

put("password", "demo1234"); 

put("expected", LoginResult.LOGIN_FAILED); 

}}, 

new HashMap<String, Object>() {{ 

put("name", "Login with email - login succeed"); 

put("email", "demo@email.com"); 

put("password", "demo1234"); 

put("expected", LoginResult.LOGIN_SUCCEED); 

}})); 

The Login feature is now finished with implementation of the TDD process. TDD is like thinking from the back to the front, like playing a Maze game from the finish to the start line. The hindrances are always there, but the approach is different.

TDD Workflow

TDD cycle will be RED -> GREEN -> REFACTOR -> RED -> .. and so on. When will it finish? When every condition is covered in positive and negative way, which means 100% green. Keep refactoring, removing all scope or functions that are not needed, and remove re-codes until everything is clean and covered so we can continue to the next feature.

source: brainhub.eu 

If best practice TDD is done, the resultant product will be good, and the quality of code will be better, cleaner, and easier to read. This greatly enhances teamwork.

BDD: Behaviour-Driven Development

BDD, Behaviour-driven development is complimentary to TDD, not an alternative. Like TDD, BDD starts when a document is set and can run simultaneously with TDD if there are sufficient testers. BDD also acts as the first user before the product is launched to market. BDD is usually used by developers, testers, product owners.

A practical approach to the BDD process may use a tool like Cucumber. The process is similar to TDD – starting with a test based on behaviour like tapping a button, typing text, etc. Starting from red, we aim to make all items green by refactoring until all needs or conditions are covered. Unlike TDD, BDD involves thinking from the start of the application flow and following the steps involved in running the application. For example, open an app, type an email address in a text field, type a password in a text field, tap a button, then go to home and see if there are error responses indicating that something has gone wrong. What makes BDD even better is that, even if the login feature changed, the BDD process remains the same as long as the behaviour of the feature remains consistent.

Complement BDD with TDD 

BDD can be run without an application UI, because BDD only captures behaviour (not the UI). Refer to the sample login feature or login case in the TDD section above). The fun thing about BDD is that the tests use normal sentences like human communication. The “syntax ” of BBD can be like this:

image 7

Scenario: Plans what we are going to do 
Given: The Context 
When: The Event that triggers what we are going to do 
Then: The Result 

From the syntax scenario above, with the Cucumber framework the class object represented in BBD will be: 

image 8

The first step is always easy – if it is red (error/fails) then make it green (success). After several refactors and adjustments, the next step of BDD for login feature will look like this:

image 9

The Login feature is now finished and the coverage is 100% – all green and all conditions covered. The complimentary function of BDD is that the test done based on behaviours from the beginning – just as a real user would do. A specification may exactly specify the behaviour of the component being tested but is less meaningful to a business user. As a result, specification-based testing is seen in BDD practice as complementary to story-based testing and operates at a lower level. Specification testing is often seen as a replacement for free-format unit testing.

To sum up, TDD and BDD are no less important than any other programming language. This process is even more important if there is no tester in the team. TDD and BBD can act as the first user before the product launches into the market.

Author:
Moch Rahman Hakim H – Senior Tester

Share

Get the latest news from us to your inbox

(Weekly newsletter)

Leave a comment



from Indonesia:

from Australia:

from New Zealand:

from Singapore:

from other countries:

© Copyright 1991 - 2020 Mitrais