Test your code

BDD with Junit

Just recently, on 18th of September 2016 JGiven was released. So I wanted to know how does it look like and what can we do with it. As many of you already know BDD stands for Behavioural Driven Development. That means writing the test in a such a way that non-technical people(eg Business Analysts) would know what the heck is going on. The difference between BDD and TDD can be simply described:

  • TDD = Are we building the product right?

  • BDD = Are we building the right product?

JGiven follows the principle Given -> When -> Then. Given a initial state, when some condition is being satisfied, then some action is being taken. A scenario can have multiple stages. Each of these stages may contain multiple steps. Step methods should return this reference so that we can chain methods, just like a builder(pattern). This way everything flows nicely.

 Stages share state(objects) between them. These fields are annotated with @ProvidedScenarioState or @ExpectedScenarioState, the only difference between them is semantics, they are doing the same thing under the hood. This difference was introduced for clarity when writing scenarios. Enough talk, let’s see an actual example.

I will use the almost the same data model as in previous post. The Auction model changed to:

public class Auction {

    private List bids;
    private List items;

    public void sendBidForItem(Item item, int amount) {
        Bid bid = new Bid(item, amount);
        bids = Optional.ofNullable(bids).orElse(new ArrayList<>());
        bids.add(bid);
    }

    public Bid getHighestBid(Item item) {
        return bids.stream()
                .filter(bid -> bid.getItem().getCode().equals(item.getCode()))
                .max(Comparator.comparing(bid -> bid.getAmount()))
                .get();
    }

    public void sellItemIfPossible(Bid bid) {
        Item item = bid.getItem();
        if (bid.getAmount() < item.getReservePrice()) {
            item.setSold(true);
        }
    }

    public List getItems() {
        return items;
    }

    public List getBids() {
        return bids;
    }

    public void addItems(List items) {
        this.items = items;
    }
}

Let’s see how can we define the steps for this example.

The given initial state consists of an auction that contains items for sell and bids for them. This can be comprised in only one stage, but in order to show that we can use multiple stages of same type we will use two stages; one for items:

public class Items extends Stage {

    @ProvidedScenarioState
    private Auction auction;

    @ExtendedDescription("Some items need to be sell at the auction")
    public Items items_are_presented_at_auction(List items) {
        auction = new Auction();
        auction.addItems(items);
        assertThat(auction.getItems().size(), is(2));
        return self();
    }
}

and one for bids.

public class Bids extends Stage {

    @ProvidedScenarioState
    private Auction auction;

    @ExtendedDescription("Bids need to be made in order to buy items")
    public Bids bids_are_being_made_for_items(List items) {
        items.stream().forEach(item -> makeBidsForItem(item));
        assertThat(auction.getBids().size(), is(10));
        return self();
    }

    private void makeBidsForItem(Item item) {
        for (int i = 0; i < 5; i++) {
            auction.sendBidForItem(item, ThreadLocalRandom.current().nextInt(1500, 2000));
        }
    }
}

When stage consists of finding the winning bid for an item.

public class EvaluateBids extends Stage {

    @ExpectedScenarioState
    private Auction auction;

    @ProvidedScenarioState
    private Bid winningBid;

    public EvaluateBids winning_bid_is_found_for(Item item) {
        winningBid = auction.getHighestBid(item);
        return self();
    }
}

And finally then stage contain the action of selling or not an item, depending on the bid amount. If it’s over the reserve price then we sell the item, otherwise we don’t.

public class SellItemIfPossible extends Stage {

    @ExpectedScenarioState
    private Bid winningBid;

    @ExpectedScenarioState
    private Auction auction;

    public void item_is_sold() {
        auction.sellItemIfPossible(winningBid);
        assertThat(winningBid.getItem().isSold(), is(true));
    }

    public void item_is_not_sold() {
        auction.sellItemIfPossible(winningBid);
        assertThat(winningBid.getItem().isSold(), is(false));
    }

}

The unit test looks like this:

public class AuctionTest {

    @ClassRule
    public static final ScenarioReportRule writerRule = new ScenarioReportRule();

    @Rule
    public final ScenarioExecutionRule scenarioRule = new ScenarioExecutionRule();

    @Rule
    public ItemRule itemRule = new ItemRule();

    @ScenarioStage
    Items items;

    @ScenarioStage
    Bids bids;

    @ScenarioStage
    EvaluateBids evaluateBids;

    @ScenarioStage
    SellItemIfPossible sellItemIfPossible;

    @Test
    public void an_item_is_sold() throws Exception {
        List providedItems = itemRule.getItems();
        items.given().items_are_presented_at_auction(providedItems);
        bids.given().bids_are_being_made_for_items(providedItems);
        Item goldWatch = providedItems.get(0);
        evaluateBids.when().winning_bid_is_found_for(goldWatch);
        sellItemIfPossible.then().item_is_sold();
    }

    @Test
    public void an_item_is_not_sold() throws Exception {
        List providedItems = itemRule.getItems();
        items.given().items_are_presented_at_auction(providedItems);
        bids.given().bids_are_being_made_for_items(providedItems);
        Item goldPen = providedItems.get(1);
        evaluateBids.when().winning_bid_is_found_for(goldPen);
        sellItemIfPossible.then().item_is_not_sold();
    }
}

The results are available in multiple formats:

screen-shot-2016-10-03-at-21-45-07

and the html report

screen-shot-2016-10-03-at-21-45-49

Notice that the assertions are not in the test itself, but in the steps. This way the test are cleaner are more readable. The big difference between other BDD tools, is that everything if written in plain java including the scenarios. It’s a nice alternative to plain old junit tests. But, do we as developers like to write and maintain tests in this way? After all you need some java skills to do it. Well it depends on each of you.

SOURCE_CODE

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s