Core Java

Importance of given-when-then in unit tests and TDD

Recently, I’ve been writing rather about more advanced concepts related to automatic testing (mostly related to Spock). However, conducting my testing training I clearly see that very often knowledge of particular tools is not the main problem. Even with Spock it is possible to write bloated and hard-to-maintain test, breaking (or not being aware of) good practices related to writing unit tests. Therefore, I decided to write about more fundamental things to promote them and by the way have a ready to use material to reference when coaching less experienced colleagues.

Introduction

Well written unit tests should meet several requirements and it is a topic for the whole series. In this blog post I would like to present a quite mature concept of dividing an unit test on 3 separate blocks with a strictly defined function (which in turn is a subset of Behavior-driven Development).

Unit tests are usually focused on testing some specific behavior of a given unit (usually one given class). As opposed to acceptance tests performed via UI, it is cheap (fast) to setup a class to test (a class under test) from scratch in an every test with stubs/mocks as its collaborators. Therefore, performance should not be a problem.

Sample test

To demonstrate the rules I will use a small example. ShipDictionary is a class providing an ability to search space ships based on particular criteria (by a part of a name, a production year, etc.). That dictionary is powered (energized) by different indexes of ships (ships in service, withdrawn from service, in production, etc.). In that one particular test it is tested an ability to search ship by a part of its name.

private static final String ENTERPRISE_D = "USS Enterprise (NCC-1701-D)";

@Test
public void shouldFindOwnShipByName() {
//given
ShipDatabase shipDatabase = new ShipDatabase(ownShipIndex, enemyShipIndex);
given(ownShipIndex.findByName("Enterprise")).willReturn(singletonList(ENTERPRISE_D));
//when
List foundShips = shipDatabase.findByName("Enterprise");
//then
assertThat(foundShips).contains(ENTERPRISE_D);
}

given-when-then

The good habit which exists in both Test-driven and Behavior-driven Development methodologies is ‘a priori’ knowledge what will be tested (asserted) in a particular test case. It could be done in a more formal way (e.g. scenarios written in Cucumber/Gherkin for acceptance tests) or in a free form (e.g. ad hoc noted points or just an idea of what should be tested next). With that knowledge it should be quite easy to determine three crucial things (being a separated sections) of which the whole test will consist.

given – preparation

In the first section – called given – of a unit test it is required to create a real object instance on which the tested operation will be performed. In focused unit tests there is only one class in which the logic to be tested is placed. In addition, other objects required to perform a test (named collaborators) should be initialized as stubs/mocks and properly stubbed (if needed). All collaborators have to be also injected into the object under test which usually is combined with that object creation (as a constructor injection should be a preferred technique of dependency injection).

//given
ShipDatabase shipDatabase = new ShipDatabase(ownShipIndex, enemyShipIndex);
given(ownShipIndex.findByName("Enterprise")).willReturn(singletonList(ENTERPRISE_D));

when – execution

In the when section an operation to be tested is performed. In our case it is a search request followed by result memorization in a variable for further assertion.

//when
List foundShips = shipDatabase.findByName("Enterprise");

In most cases it is good to have just one operation in that section. More elements may suggest an attempt to test more than one operation which (possibly) could be divided into more tests.

then – assertion

The responsibility of the final section – then – is mostly an assertion of the previously received result. It should be equal to the expected value.

//then
assertThat(foundShips).contains(ENTERPRISE_D);

In addition, it may be necessary to perform a verification of method executions on declared mocks. It should not be a common practice as an assertion on received value in most cases is enough to confirm that code being tested works as expected (according to set boundaries). Nevertheless, especially with testing void methods it is required to verify that a particular method was executed with anticipated arguments.

AAA aka 3A – an alternative syntax

As I’ve already mentioned, BDD is a much wider concept which is especially handy for writing functional/acceptance tests with requirements defined upfront, (often) in a non technical form. An alternative test division syntax (with very similar meaning for the sections) is arrange-act-assert often abbreviated to AAA or 3A. If you don’t use BDD at all and three A letters are easier to remember for you than GWT, it’s perfectly fine to use it to create the same high quality unit tests.

Tuning & optimization

The process of matching used tools and methodologies to ongoing process of skill acquisition (aka the Dreyfus model) has been nicely described in the book Pragmatic Thinking and Learning: Refactor Your Wetware. Of course, in many cases it may be handy to use a simplified variant of a test with a given section moved to a setup/init/before section or initialized inline. The same may apply to when and then sections which could be merged together (into an expect section, especially in parameterized tests). Having some experience and fluency in writing unit tests it is perfectly valid to use shorthand and optimizations (especially testing some non-trivial cases). As long as the whole team understand the convention and is able to remember about basic assumptions regarding writing good unit tests.

Summary

Based on my experience in software development and as a trainer I clearly see that dividing (unit) tests into sections makes them shorter and more readable, especially having less experienced people in the team. It’s simpler to fill 3 sections with concisely defined responsibility than figure out and write everything in the tests at once. In closing, particularly for people reading only the first and the last sections of the article, here are condensed rules to follow:

  • given – an object under test initialization + stubs/mocks creation, stubbing and injection
  • when – an operation to test in a given test
  • then – received result assertion + mocks verification (if needed)

P.S. It is good to have a test template set in your IDE to safe a number of keystrokes required to write every test.
P.S.S. I you found this article useful you can let me know to motivate me to write more about unit test basics in the future.

Picture credits: Tomas Sobek, Openclipart, https://openclipart.org/detail/242959/old-scroll

Self promotion. Would you like to improve your and your team testing skills and knowledge of Spock/JUnit/Mockito/AssertJ quickly and efficiently? I conduct a condensed (unit) testing training which you may find useful.

Reference: Importance of given-when-then in unit tests and TDD from our JCG partner Marcin Zajaczkowski at the Solid Soft blog.

Marcin Zajaczkowski

Marcin is an experienced architect who specializes in creating high quality software. Being under the impression of the Agile methodologies and the Software Craftsmanship movement, he believes in the value of good, testable and maintainable code. He aims to forge good software that makes the client delighted and the team proud of how the code itself looks.In his teaching, as a conference speaker, college lecturer, IT coach and trainer, he shows how to guide software development effectively using tests (with TDD, pair programming, Clean Code, design patterns, etc.) and maintaining a quality-oriented development environment (with CI, Sonar, automatic deployment, etc.).He is also a FOSS projects author and contributor, a Linux enthusiast.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button