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:

and the html report

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.
Like this:
Like Loading...