Google

Nov 6, 2013

jBehave with ExamplesTable

This extends the previous tutorials
Step 1: The story file  which supplies a list of transactions as an input (i.e. via Given) and validates against a list of transactions that are supplied as shown below in Then.

Narrative: As a txns processor verify the number of txns returned

Scenario: verify transactions 

Given a list of transactions
txnCode|txndate|txnAmount
A123|10/07/2005|500.00
A124|10/25/2005|500.00
A125|10/28/2005|500.00
A126|10/29/2005|500.00
A127|10/15/2005|500.00
A128|10/12/2005|500.00

When processTxn method is fired with accountCd = 1234

Then I expect to receive 5 transactions
Then I expect transactions to have
TransactionType|TransactionSubType|OtherIncomeExpType
Income|InterestRevenue|NotApplicable
Income|InterestRevenue|NotApplicable
Expense|OtherIncome|NotApplicable
EXPENSE|InterestRevenue|NotApplicable
INCOME|NotApplicable|NotApplicable


Step 2: Map the story to step class TransactionProcessorStep as shown below. Pay attention to the ExamplesTable class provided by jBehave library.

//...

import org.drools.runtime.StatelessKnowledgeSession;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.jbehave.core.annotations.Given;
import org.jbehave.core.annotations.Then;
import org.jbehave.core.annotations.When;
import org.jbehave.core.model.ExamplesTable;
import org.joda.time.DateTime;
import org.mockito.Mockito;
import org.springframework.stereotype.Component;

@Component
public class TransactionProcessorStep
{

    //...
 //input
 List<Transaction> transactionsList = Collections.EMPTY_LIST;
 //output
 List<Transaction> result;

    @Given("a list of transactions $cashTransactionsTable")
    public void cashTxns(ExamplesTable cashTransactionsTable)
    {
        this.transactionsList = toTransactionsList(cashTransactionsTable);
    }
 
 @When("processTxn method is fired with accountCd = $accountCode")
    public void processTransaction(String accountCode)
    {
 
        try
        {
            //logic to process actual method and get the result
   result = ...
            
        }
        catch (Exception e)
        {
            throw new RuntimeException(e);
        }
        
    }

    @Then("I expect to receive $count transactions")
    public void verify(int count)
    {
        try
        {
            MatcherAssert.assertThat(count, Matchers.equalTo(result.size()));
            
        }
        catch (Exception e)
        {
            throw new RuntimeException(e);
        }
    } 
 
 @Then("I expect transactions to have $transactionsTable")
    public void verify2(ExamplesTable transactionsTable)
    {
        try
        {
            
            int i = 0;
            for (Map<String, String> row : transactionsTable.getRows())
            {
                
                Transaction transaction = result.get(i);
                
                String txnType = row.get("TransactionType");
                MatcherAssert.assertThat(transaction.getTransactionType().name(),
                        Matchers.equalTo(txnType));
                
     String analysisCode = row.get("TransactionSubType");
                MatcherAssert.assertThat(transaction.getAnalysisCode(),
                        Matchers.equalTo(analysisCode));
    
                String otherIncomeExpType = row.get("OtherIncomeExpType");
                MatcherAssert.assertThat(transaction.getOtherIncomeAndExpensesSubType().name(),
                        Matchers.equalTo(otherIncomeExpType));
                 
                ++i;
                
            }
            
        }
        catch (Exception e)
        {
            throw new RuntimeException(e);
        }
    }
 
 private List<Transaction> toTransactionsList(ExamplesTable table)
    {
        List<Transaction> ctList = new ArrayList<Transaction>();
        for (Map<String, String> row : table.getRows())
        {
            
            //txnCode|txndate|txnAmount
            String txnCode = row.get("analysisCd");
            String txndate = row.get("txndate");
            String txnAmount = row.get("txnAmount");
   
            Transaction ct = new Transaction();
            ct.setAnalysisCd(txnCode);
            
            SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy");
            try
            {
                ct.setTxnDt(sdf.parse(txndate));
            }
            catch (ParseException e)
            {
                throw new RuntimeException(e);
            }
            ct.setTxnAmount(new BigDecimal(txnAmount));
            
            ctList.add(ct);
        }
        return ctList;
    }
 

Labels: ,

Oct 22, 2013

jBehave Tutorial

This extends the previous tutorials
To appreciate jBehave, let's look at a better example here. This example is about a science formula

  Speed = distance / Time.

So,

  • given  distance and time, calculate speed
  • given speed and time, calculate distance
  • given speed and distance, calculate time.

Step 1: The story file in plain english. speed.story under src/main/resources/jBehave folder.


Narrative: As a student, I want to practice speed formula where speed = distance / time

scenario: calculate speed from distance and time

Given distance = 10.0
Given time = 2.5
When calculate speed
Then verify speed = 4.0


scenario: calculate distance from speed and time

Given speed = 4.0
Given time = 2.5
When calculate distance
Then verify distance = 10.00

scenario: calculate time from speed and distance

Given speed = 4.0
Given distance = 10.0
When calculate time
Then verify time = 2.50


Step 2: Mapping the story to Java step class. The steps are fine grained so that they can be mixed and matched depending on th scenario.

package com.mycompany.jbehave2;

import org.jbehave.core.annotations.Given;
import org.jbehave.core.annotations.Named;
import org.jbehave.core.annotations.Then;
import org.jbehave.core.annotations.When;
import org.jbehave.core.steps.Steps;
import org.springframework.util.Assert;

public class SpeedVelocityFormulaSteps extends Steps
{
    
    SpeedVelocityFormaula svf = new SpeedVelocityFormulaImpl();
    
    private double speed;
    private double distance;
    private double time;
    
    private double resultSpeed;
    private double resultDistance;
    private double resultTime;
    
    @Given("speed = $speed")
    public void speedInput(@Named("speed") double speed)
    {
        this.speed = speed;
    }
    
    @Given("distance = $distance")
    public void distanceInput(@Named("distance") double distance)
    {
        this.distance = distance;
    }
    
    @Given("time = $time")
    public void timeInput(@Named("time") double time)
    {
        this.time = time;
    }
    
    @When("calculate speed")
    public void calcSpeed()
    {
        Assert.notNull(distance);
        Assert.notNull(time);
        
        resultSpeed = svf.calculateSpeed(this.distance, this.time);
    }
    
    @When("calculate distance")
    public void calcDistance()
    {
        Assert.notNull(speed);
        Assert.notNull(time);
        
        resultDistance = svf.calculateDistance(this.speed, this.time);
    }
    
    @When("calculate time")
    public void calcTime()
    {
        Assert.notNull(speed);
        Assert.notNull(distance);
        
        resultTime = svf.calculateTime(this.speed, this.distance);
    }
    
    @Then("verify speed = $speedSupplied")
    public void verifySpeed(double speedSupplied)
    {
        Assert.notNull(distance);
        Assert.notNull(time);
        
        junit.framework.Assert.assertEquals(speedSupplied, resultSpeed);
    }
    
    @Then("verify distance = $distanceSupplied")
    public void verifyDistance(double distanceSupplied)
    {
        Assert.notNull(this.speed);
        Assert.notNull(time);
        
        junit.framework.Assert.assertEquals(distanceSupplied, resultDistance);
    }
    
    @Then("verify time = $timeSupplied")
    public void verifyTime(double timeSupplied)
    {
        Assert.notNull(speed);
        Assert.notNull(distance);
        
        junit.framework.Assert.assertEquals(timeSupplied, resultTime);
    }
    
}

Step 3: The actual interface that performs the calculations.

package com.mycompany.jbehave2;

public interface SpeedVelocityFormaula
{
    abstract double calculateSpeed(double distance, double time);
    
    abstract double calculateDistance(double speed, double time);
    
    abstract double calculateTime(double speed, double distance);
}

Step 4: The implementation. This is the class under test.


package com.mycompany.jbehave2;

public class SpeedVelocityFormulaImpl implements SpeedVelocityFormaula
{  
    public double calculateSpeed(double distance, double time)
    {
        return distance / time; // s = d/t
    }
    
    public double calculateDistance(double speed, double time)
    {
        return speed * time; //speed * time
    }
    
    public double calculateTime(double speed, double distance)
    {
        return distance / speed;  //distance/speed
    }
    
}


Step 5: Finally the jUnit test class

package com.mycompany.jbehave2;

import de.codecentric.jbehave.junit.monitoring.JUnitReportingRunner;

import java.util.Arrays;
import java.util.List;

import org.jbehave.core.junit.JUnitStories;
import org.jbehave.core.steps.InjectableStepsFactory;
import org.jbehave.core.steps.InstanceStepsFactory;
import org.junit.runner.RunWith;

@RunWith(JUnitReportingRunner.class)
public class JBehaveUnitTest extends JUnitStories
{
    
    public JBehaveUnitTest()
    {
        super();
    }
    
    public InjectableStepsFactory stepsFactory()
    {
        return new InstanceStepsFactory(configuration(), new SpeedVelocityFormulaSteps());
    }
    
    @Override
    protected List<String> storyPaths()
    {
        return Arrays.asList("jbehave/speed.story");
    }
    
}

Step 6: Run th unit test.



Labels: ,

Oct 16, 2013

jBehave -- Behavioural Driven Development (BDD)

Q. What is BDD?
A. BDD is principally an idea about how software development should be managed by both business interests and technical insight. Test-driven development focuses on the developer’s opinion on how parts of the software should work. Behavior-driven development focuses on the users’ opinion on how they want your application to behave. So, when you start writing a test, you need to think about the stories, and each story should cover three things:

  • Given :  an input value of 2
  • When : you multiply  the input with 3
  • Then : result should be 6

Even you write unit tests as part of TDD (Test Driven Development) or without TDD , you need to think about Given ... When ... Then ...

Here is a simple  example using the jBehave framework in Java. jBehave tutorial.

Step 1:  Maven pom.xml file on jBehave dependency


 
<dependency>
 <groupId>org.jbehave</groupId>
 <artifactId>jbehave-core</artifactId>
 <version>3.8</version>
</dependency>


Step 2: Define the story in plain English that business users and testers can understand using Given... When Then... style. The math.story file under  src/main/resources/jbehave folder


 
Scenario: 2 squared

Given a variable input with value 2
When I multiply input by 2 
Then result should equal 4

Scenario: 3 squared

Given a variable input with value 3
When I multiply input by 3 
Then result should equal 9



Step 3: Map the above scenarios based stories to Java equivalent.

 
package com.mycompany.jbehave;

import org.jbehave.core.annotations.Given;
import org.jbehave.core.annotations.Named;
import org.jbehave.core.annotations.Then;
import org.jbehave.core.annotations.When;
import org.jbehave.core.steps.Steps;

public class MathSteps extends Steps
{
    private int input;
    private int result;
    
    @Given("a variable input with value $value")
    public void givenInputValue(@Named("value") int value)
    {
        input = value;
    }
    
    @When("I multiply input by $value")
    public void whenImultiplyInputBy(@Named("value") int value)
    {
        result = input * value;
    }
    
    @Then("result should equal $value")
    public void thenInputshouldBe(@Named("value") int value)
    {
        if (value != result)
            throw new RuntimeException("result is " + result + ", but should be " + value);
    }
}


Step 4: Write a main class to excute the scenarios.

 
package com.mycompany.jbehave;

import java.util.Arrays;
import java.util.List;

import org.jbehave.core.embedder.Embedder;

public class JBehaveTest
{
    private static Embedder embedder = new Embedder();
    private static List<String> storyPaths = Arrays
            .asList("jbehave/math.story");
    
    public static void main(String[] args)
    {
        embedder.candidateSteps().add(new MathSteps());
        try
        {
            embedder.runStoriesAsPaths(storyPaths);
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        
    }
}


Step 5: Run it to get output:

 
Processing system properties {}
Using controls EmbedderControls[batch=false,skip=false,generateViewAfterStories=true,ignoreFailureInStories=false,ignoreFailureInView=false,verboseFailures=false,verboseFiltering=false,storyTimeoutInSecs=300,threads=1]
Running story jbehave/math.story
Generating reports view to 'C:\projects\my-app-parent\my-app\target\jbehave' using formats '[]' and view properties '{defaultFormats=stats, decorateNonHtml=true, viewDirectory=view, decorated=ftl/jbehave-report-decorated.ftl, reports=ftl/jbehave-reports-with-totals.ftl, maps=ftl/jbehave-maps.ftl, navigator=ftl/jbehave-navigator.ftl, views=ftl/jbehave-views.ftl, nonDecorated=ftl/jbehave-report-non-decorated.ftl}'
Reports view generated with 0 stories (of which 0 pending) containing 0 scenarios (of which 0 pending)


Now, modify the math.story file to have an error for the second  scenario as shown below. 3 * 3 is not 10.

 
Scenario: 2 squared

Given a variable input with value 2
When I multiply input by 2 
Then result should equal 4

Scenario: 3 squared

Given a variable input with value 3
When I multiply input by 3 
Then result should equal 10


Run it again with the change, and you will get an error as shown below for the second scenario.

 
Processing system properties {}
Using controls EmbedderControls[batch=false,skip=false,generateViewAfterStories=true,ignoreFailureInStories=false,ignoreFailureInView=false,verboseFailures=false,verboseFiltering=false,storyTimeoutInSecs=300,threads=1]
Running story jbehave/math.story
Generating reports view to 'C:\projects\my-app-parent\my-app\target\jbehave' using formats '[]' and view properties '{defaultFormats=stats, decorateNonHtml=true, viewDirectory=view, decorated=ftl/jbehave-report-decorated.ftl, reports=ftl/jbehave-reports-with-totals.ftl, maps=ftl/jbehave-maps.ftl, navigator=ftl/jbehave-navigator.ftl, views=ftl/jbehave-views.ftl, nonDecorated=ftl/jbehave-report-non-decorated.ftl}'
Reports view generated with 0 stories (of which 0 pending) containing 0 scenarios (of which 0 pending)
org.jbehave.core.embedder.Embedder$RunningStoriesFailed: Failures in running stories: 
jbehave/math.story: org.jbehave.core.embedder.StoryManager$StoryExecutionFailed: jbehave/math.story
 at org.jbehave.core.embedder.Embedder$ThrowingRunningStoriesFailed.handleFailures(Embedder.java:495)
 at org.jbehave.core.embedder.Embedder.handleFailures(Embedder.java:224)
 at org.jbehave.core.embedder.Embedder.runStoriesAsPaths(Embedder.java:205)
 at com.mycompany.jbehave.JBehaveTest.main(JBehaveTest.java:19)



Next post will use junit for testing the scenarios.

Labels: , ,