Test Driven Development: best practices, solutions, tips and many more
Header image

TDD is like cooking (hehe)

Posted by wojtek on April 9th, 2012 - (0 Comments)

I want to make a comparison between my cooking, and my TDD. I started learning how to cook about the same time when I started learning TDD.

Let us discuss the leek soup I made loads of times :) The first time I made it I did not look at any recipes, I just followed my heart. It was awesome, tasted very good! The second time I made it, it was almost impossible to eat and I was wondering what happened. It was horrible. But I did everything almost exactly the same?
Then I started looking into the recipes and found that some things should be added to a leek soup and some not. Some spices are needed in a leek soup, some should be avoided. The ratio of water, leeks, butter was also a very important thing. Slight variations in those thing could make the soup very good or very bad. After following the recipe several times for couple of different soups I started to see the patterns. Even though I have still a lot to learn, I know a bit more how to make a good soup without a recipe. I just can see if it is going to be the kind of soup I like just by looking at the ingredients!

It more or less looks like I am in the third stage of competence when it comes to preparing a leek soup ;) hehe

How did I get there? Using the tool called “recipes”!

How can you get to the next level of competence in software development? Using the tools and practicing as well. The best tool I have found so far is called TDD! Once you are competent you probably will not need the tool, you will just “feel” when something is good or not, you might find that it acctualy slows you down. But for beginners, TDD is in my opinion a very good way of working. Following the very simple rules of TDD allows you to unconsciously produce code that is more likely of high quality and compliant with the harder to understand rules of high cohesion and loose coupling. Following the very simple “red-green-refactor” and “test should be simple” rules is more likely to produce code that is compliant with the harder to understand (or easier misinterpret) SOLID rules, etc.

P.S.
The next tool I would recommend is pair-programming.

P.S. 2

I have just finished reading the new book by Robert C. Martin titled “The Clean Coder: A Code of Conduct for Professional Programmers”. Highly recommended! :)

 

Devs write tests, not testers

Posted by wojtek on December 2nd, 2011 - (0 Comments)

Tests should be written by developers not by testers. Period.

Why?

Basically developers seem to be better in writing test code then testers since they specialise in coding. Testers are better in other areas!
Developers are more likely to produce maintainable tests then testers. Developers are also more likely to be faster.

I noticed that testers produce lots of duplication in tests. Unfortunately in most cases they are not familiar with the best practices when it comes to coding (DRY, YAGNI, SOLID, LOD, etc…) and that might cause and in most cases causes maintenance problems.

What about producing acceptance test scenarios? Well, in that case I would recommend getting together and working on the scenario skeleton, for example a business person, a tester and a developer. Then the developer can implement the test based on the skeleton and consult the end result with business and testers once again.

So for example, who writes selenium tests that scrap the applications and test the whole business flows? Developers! What do testers do? They help when that test scenarios are sketched. Then they wait for the production code to be ready and test the performance (when there are no automatic performance tests) and the boundary conditions that the developers might not have thought about.

This is a revisited version of an old example

This is a new implementation of the same example using yatspec but in a more clear (in my oppinion) way. The old post is located here.

Why clearer? We are not using unnecessary objects like StateExtractor, ActionUnderTest and hamcrest matchers which we do not like.

What is the story?

The story we will be implementing is “Buy the stocks when price drops by 10 units”. When we receive a notification through a web service that we expose, we will have to examine it and buy stocks when certain conditions are met.

What is Yatspec?

Yatspec is a new test framework that can meet your needs if Cucumber, Concordion and Fitnesse don’t. This example will focus on getting the story done using Yatspec. I will prepare other posts on using other acceptance test frameworks soon. There will be also another post that compares the frameworks. I will link to then as soon as I implement them.

Step 1: Start with an acceptance test skeleton

I came up with an acceptance test skeleton. This is how it looks like:

@RunWith(SpecRunner.class)
public class BuyStocksAcceptanceTest extends TestState {
    @Test
    public void buyStocksWhenPriceDrops() throws Exception {
        whenANotificationThatGoogleStockPriceDroppedBy10UnitsWasReceived();
        thenABiddingRequestFor1000googleStocksWasSentToStockMarket();
    }
 
    private void thenABiddingRequestFor1000googleStocksWasSentToStockMarket() {
        throw new UnsupportedOperationException("not yet implemented");
    }
 
    private void whenANotificationThatGoogleStockPriceDroppedBy10UnitsWasReceived() {
        throw new UnsupportedOperationException("not yet implemented");
    }
}

When I run it I see of course:

java.lang.UnsupportedOperationException: not yet implemented
	at com.testdrivendevelopment.example.acceptance.BuyStocksAcceptanceTest.whenANotificationThatGoogleStockPriceDroppedBy10UnitsWasReceived(BuyStocksAcceptanceTest.java:21)
	at com.testdrivendevelopment.example.acceptance.BuyStocksAcceptanceTest.buyStocksWhenPriceDrops(BuyStocksAcceptanceTest.java:12)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	(...)
Html output:
/tmp/com/testdrivendevelopment/example/acceptance/BuyStocksAcceptanceTest.html

When I open the output HTML file I see:

Notice that the HTML file is generated from Java code. This makes it very easy to refactor all Java stuff and keep documentation always up to date, since the documentation is generated. You don’t have to edit any HTML files!

Step 2: Time to implement the acceptance tests

I implement the aceptance tests. I have never used Jersey before, but I decided to do it this time since it is said to be the leading solution when it comes to RESTful communication in Java. Please forgive me if my use of Jersey is not optimal. Please remember, in this article we focus on introducing Yatspec.

Please notice that we have an Application object that represents our application. We also have a StockMarketStubServer which is a stub of the real stock market that we will be sending requests to.

As you can see below the acceptance test sends a notification to the application and the verifies that the stock market received the expected bid request.

I also add information to interestingGivens and log(…). Those elements will be visible in the HTML output.

 
@RunWith(SpecRunner.class)
public class BuyStocksAcceptanceTest extends TestState {
    private final Application application = new Application();
    private final StockMarketStubServer stockMarketStubServer = new StockMarketStubServer();
    private final String googleStockName = "Google";
 
    @Before
    public void setUp() throws Exception {
        application.start();
        stockMarketStubServer.start();
    }
 
    @After
    public void tearDown() throws Exception {
        application.stop();
        stockMarketStubServer.stop();
    }
 
    @Test
    public void buyStocksWhenPriceDrops() throws Exception {
        whenANotificationThatGoogleStockPriceDroppedBy10UnitsWasReceived();
        thenABiddingRequestFor1000googleStocksWasSentToStockMarket();
    }
 
    private void thenABiddingRequestFor1000googleStocksWasSentToStockMarket() {
        String requestSentToStockMarket = stockMarketStubServer.popOnlyRequest();
        log("Request received by stock market system", requestSentToStockMarket);
 
        int expectedNumUnits = 1000;
        interestingGivens.add("Expected stocks to buy", expectedNumUnits);
        Message message = stockMarketStubServer.popOnlyMessage();
        assertThat(message.getRequestType()).isEqualTo(BID);
        assertThat(message.getStockName()).isEqualTo(googleStockName);
        assertThat(message.getUnits()).isEqualTo(expectedNumUnits);
    }
 
    private void whenANotificationThatGoogleStockPriceDroppedBy10UnitsWasReceived() {
        String priceDrop = "-10";
 
        WebResource webResource = postNotificationToApplication(priceDrop);
 
        interestingGivens.add("Price drop", priceDrop);
        interestingGivens.add("Stock name", googleStockName);
        log("Received notification", webResource);
    }
 
    private WebResource postNotificationToApplication(String priceDrop) {
        DefaultClientConfig clientConfig = new DefaultClientConfig();
        Client client = Client.create(clientConfig);
        WebResource resource = client.resource(application.getBaseUri());
        WebResource webResource = resource.path("/notification")
                .queryParam("stockName", googleStockName)
                .queryParam("units", priceDrop);
        webResource.post();
        return webResource;
    }
}

Step 3: Make the acceptance test pass

I implement the production code. I don’t include the listings in here since the purpose of this article is to focus on Yatspec. If you wish to see the end result please take a look at the attached sources.

Yatspec final HTML output

I run the test. Now we can take a look at the final Yatspec documentation/output, HTML!
You can click the headers “Interesting Givens”, “Received Notification” and “Request Received By Stock Market System” to expands those sections.

Notice that we can log the conversations with other systems. One can use the CapturedInputAndOutputs object, that is the log(…) method that will be automaticly added to the HTML output. Notice that we can highligt interesting stuff in the conversations without editing HTML, using IntegestingGives object! When you are doing ATDD in backend systems this “logging” might be very useful. I would say that in most cases the “captured inputs and outputs” are actually the acceptance criteria.

Conclusions

This post was just a simple quick introduction to Yatspec. I am working on comparing Yatspec with other test frameworks. I will update the posts as soon as I am ready. Please comment and subscribe to RSS! :)

Avoid argument captors

Posted by wojtek on September 19th, 2011 - (0 Comments)

Why avoid argument captors?

They decrease readability of the tests. It is a code smell. It is very probable that if you use argument captor your class violates one of the SOLID rules. The most common violation in my experience is not sticking to SRP.

Example 1

I have found on the web an example of how to use argument captors. This is the test I found:

    @Test
    public void testInviteJohn() {
        ArgumentCaptor<Person> argument = ArgumentCaptor.forClass(Person.class);
        Party party = mock(Party.class);
 
        Person john = new Person("John");
        Person peter = new Person("Peter");
 
        // Peter invites John to the party
        peter.inviteToParty(john, party);
 
        verify(party).addGuest(argument.capture());
        // verify John was invited
        assertEquals("John", argument.getValue().getName());
    }

The author had probably in mind showing how argument captors work. I agree that it is a good thing to show stuff on the web. On the other hand if I was test driving implementation of inviteToParty method I would go for just a simple verify:

    private final Party party = mock(Party.class);
    private final Person personInvited = mock(Person.class);
    private final String personName = "arbitraryPersonName";
    private final Person person = new Person(personName);
 
    @Test
    public void inviteToParty() throws Exception {
        person.inviteToParty(personInvited, party);
 
        verify(party).addGuest(personInvited);
    }

In my opinion it is far more readable. I don’t see the point in using argument captor in this case.

Example 2

I found another example usage of argument captor on the web.
The solution presented there looks like this:

public class MailDelivererTest {
    @InjectMocks
    private MailDeliverer subject = new MailDeliverer();
 
    @Mock
    private ExternalMailSystem externalMailSystem;
 
    @Captor
    private ArgumentCaptor<Email> emailCaptor;
 
    @Before
    public void injectDoubles() {
        MockitoAnnotations.initMocks(this);
    }
 
    @Test
    public void sendsEmailByConstructingEmailObject() {
        String expectedUser = "tim";
        String expectedDomain = "wingfield.com";
        String expectedBody = "Hi Tim!";
 
        subject.deliver(expectedUser + "@" + expectedDomain, expectedBody);
 
        verify(externalMailSystem).send(emailCaptor.capture());
        Email email = emailCaptor.getValue();
        assertThat(email.getUser(), is(expectedUser));
        assertThat(email.getDomain(), is(expectedDomain));
        assertThat(email.getBody(), is(expectedBody));
    }
}

This is a clean test. I like it. But If I was to write that test I would probably split that MailDeliverer. I would split the the creation of Email object and invoking external system. When you order a laptop you don’t have to assemble it yourself, it is assembled for you in a factory. Some may say that this is a bit over the top in this case, but this is just to stress the principle I have in mind. This is what I came up with:

public class MyMailDelivererTest {
    private final ExternalMailSystem externalMailSystem = mock(ExternalMailSystem.class);
    private final EmailFactory emailFactory = mock(EmailFactory.class);
    private final Email email = mock(Email.class);
 
    private final String mailBody = "Mail body.";
    private final String emailAddress = "user@domain.com";
 
    private final MyMailDeliverer mailDeliverer = new MyMailDeliverer(emailFactory, externalMailSystem);
 
    @Test
    public void deliverMail() throws Exception {
        given(emailFactory.create(emailAddress, mailBody)).willReturn(email);
 
	mailDeliverer.deliver(emailAddress, mailBody);
 
	verify(externalMailSystem).send(email);
    }
}

And:

public class EmailFactoryTest {
    private final EmailFactory emailFactory = new EmailFactory();
 
    @Test
    public void createEmail() throws Exception {
        Email email = emailFactory.create("user@domain.com", "Mail body.");
 
        assertThat(email.getUser()).isEqualTo("user");
        assertThat(email.getDomain()).isEqualTo("domain.com");
        assertThat(email.getBody()).isEqualTo("Mail body.");
    }
}

What do YOU think? :) Please comment and subscribe to RSS! :)

Commit to SCM frequently

Posted by wojtek on September 4th, 2011 - (0 Comments)

I try to commit and update as frequent as possible. The ideal scenario is to commit and update after we have finished a small chunk of work. That might be even each 10 minutes! This approach encourages thinking in small steps. If an unexpected test fails we can revert without much cost. If the refactor we are doing goes not as we expected we can revert and try once again. It some cases it will be cheaper to revert than keep doing something that looks not pleasent form the beginning. It worked for me many times.

Please comment and subscribe to RSS! :)