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 injectionwhen
– an operation to test in a given testthen
– 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. |