Saturday, February 14, 2015

JBehave - getting started

First of all - what is JBehave ? About that, it's better to read from it original site : http://jbehave.org/ : 

JBehave is a framework for Behaviour-Driven Development (BDD). BDD is an evolution of test-driven development (TDD) and acceptance-test driven design, and is intended to make these practices more accessible and intuitive to newcomers and experts alike. It shifts the vocabulary from being test-based to behaviour-based, and positions itself as a design philosophy. 

In shorts, it's addon for JUnit,which is giving possibilities to make testing process more transparent to business(non-developer) users. For example, with JBehave we can write test descriptions in plain text (.story) files. 

0. Goal. 

As a developer , I want to test my ActiveMQ application using BDD methodology.
Component of my application:
Active MQ getting started -1: Producer/Consumer(Sender/Receiver) 



So, I want to re-write my JUnit tests using JBehave.

My test cases :
1. Producer/Consumer : simple test, 1 message have to be received by 1 Consumer.
2. Producer/Consumer : multiple test, with several consumers of one queue, 1 message have to be received by only 1 Consumer.
3. Publisher/Subscriber : all subscribers of topic have to receive the message. 

1. Stories

What is "story"?
From jbehave.com:
Behaviour-Driven Development encourages you to start defining the stories via scenarios that express the desired behaviour in a textual format, e.g.:
Given a stock of symbol STK1 and a threshold of 10.0
When the stock is traded at 5.0
Then the alert status should be OFF
The textual scenario should use the language of the business domain and shield away as much as possible the details of the technical implementation. Also, it should be given a name that is expressive of the functionality that is being verified, i.e. trader_is_alerted_of_status.story.

So,let's re-write our test cases from above,to "stories" syntax.
For that I created 2 files in test/resource directory: ProducerConsumer.story and PublisherSubscriber.story. In these files, I have to re-formulate test cases,using BDD syntax(Scenario, Give, When, Then).

My test stories files: 

ProducerConsumer.story : 
Scenario: simple message receiving by one consumer
Given one producer, one consumer, one message
When producer sends message to queue
Then consumer receives message from queue

Scenario: simple message receiving by several consumers:only one of them have to receive message
Given one producer, several consumers, one message
When producer sends message to queue
Then only one consumer have to receive message from queue

PublisherSubscriber.story: 
Scenario: broadcast message receiving by all subscribers of topic
Given one publisher, several subscribers, one message
When publisher sends message
Then all subscribers have to receive this message

2. Test classes

Now we need JUnit tests which have to cover our stories.  I created 2 files (each for each story file) : PublisherSubscriberJB and ProducesConsumerJB. They are empty now, we will implement them later.

ProducesConsumerJB:

package com.demien.amq.jbehave;

/**
 * Created by dmitry on 14.02.15.
 */
public class ProducerConsumerJB {
}


PublisherSubscriberJB:

package com.demien.amq.jbehave;

/**
 * Created by dmitry on 09.02.15.
 */
public class PublisherSubscriberJB {


}


3. JBehave runner.

As I mentioned before, JBehave  - addon on JUnit. So, we have to create jbehave-runner file,which will make our JUnit tests consider our stories files.
I copy/pasted it from one of examples from jbehave site.

package com.demien.amq.jbehave;

import org.jbehave.core.Embeddable;
import org.jbehave.core.configuration.Configuration;
import org.jbehave.core.configuration.MostUsefulConfiguration;
import org.jbehave.core.i18n.LocalizedKeywords;
import org.jbehave.core.io.LoadFromClasspath;
import org.jbehave.core.io.StoryFinder;
import org.jbehave.core.junit.JUnitStories;
import org.jbehave.core.model.ExamplesTableFactory;
import org.jbehave.core.parsers.RegexStoryParser;
import org.jbehave.core.reporters.StoryReporterBuilder;
import org.jbehave.core.steps.InjectableStepsFactory;
import org.jbehave.core.steps.InstanceStepsFactory;
import org.jbehave.core.steps.ParameterConverters;

import java.text.SimpleDateFormat;
import java.util.List;

import static org.jbehave.core.io.CodeLocations.codeLocationFromClass;
import static org.jbehave.core.reporters.Format.CONSOLE;
import static org.jbehave.core.reporters.Format.HTML;
import static org.jbehave.core.reporters.Format.TXT;
import static org.jbehave.core.reporters.Format.XML;

/**
 * Created by dmitry on 09.02.15.
 */
public class JBehaveRunner extends JUnitStories {

        public JBehaveRunner() {
            configuredEmbedder().embedderControls().doGenerateViewAfterStories(true).doIgnoreFailureInStories(true)
                    .doIgnoreFailureInView(true).useThreads(1).useStoryTimeoutInSecs(60);
        }

        @Override
        public Configuration configuration() {
            Class<? extends Embeddable> embeddableClass = this.getClass();
            // Start from default ParameterConverters instance
            ParameterConverters parameterConverters = new ParameterConverters();
            // factory to allow parameter conversion and loading from external resources (used by StoryParser too)
            ExamplesTableFactory examplesTableFactory = new ExamplesTableFactory(new LocalizedKeywords(), new LoadFromClasspath(embeddableClass), parameterConverters);
            // add custom converters
            parameterConverters.addConverters(new ParameterConverters.DateConverter(new SimpleDateFormat("yyyy-MM-dd")),
                    new ParameterConverters.ExamplesTableConverter(examplesTableFactory));
            return new MostUsefulConfiguration()
                    .useStoryLoader(new LoadFromClasspath(embeddableClass))
                    .useStoryParser(new RegexStoryParser(examplesTableFactory))
                    .useStoryReporterBuilder(new StoryReporterBuilder()
                            .withCodeLocation(codeLocationFromClass(embeddableClass))
                            .withDefaultFormats()
                            .withFormats(CONSOLE, TXT, HTML, XML))
                    .useParameterConverters(parameterConverters);
        }

        @Override
        public InjectableStepsFactory stepsFactory() {
            return new InstanceStepsFactory(configuration(), new PublisherSubscriberJB(), new ProducerConsumerJB());
        }

        @Override
        protected List<String> storyPaths() {
            return new StoryFinder().findPaths(codeLocationFromClass(this.getClass()), "*.story", "*excluded*.story");

        }
}

Most interesting thing here is function stepsFactory - here we have to list our "steps"  - JUnit tests which have to cover our stories, which we created on previous steps.


4. First run

Now we can run our JBehave runner(just as regular JUnit test) to see if everything is fine: we have to be sure that JBehave have found our .story files.
My output after run:

Processing system properties {}
Using controls EmbedderControls[batch=false,skip=false,generateViewAfterStories=true,ignoreFailureInStories=true,ignoreFailureInView=true,verboseFailures=false,verboseFiltering=false,storyTimeoutInSecs=60,failOnStoryTimeout=false,threads=1]

(BeforeStories)

Running story ProducerConsumer.story

(ProducerConsumer.story)
Scenario: simple message receiving by one consumer
Given one producer, one consumer, one message (PENDING)
When producer sends message to queue (PENDING)
Then consumer receives message from queue (PENDING)
@Given("one producer, one consumer, one message")
@Pending
public void givenOneProducerOneConsumerOneMessage() {
  // PENDING
}

@When("producer sends message to queue")
@Pending
public void whenProducerSendsMessageToQueue() {
  // PENDING
}

@Then("consumer receives message from queue")
@Pending
public void thenConsumerReceivesMessageFromQueue() {
  // PENDING
}


Scenario: simple message receiving by several consumers:only one of them have to receive message
Given one producer, several consumers, one message (PENDING)
When producer sends message to queue (PENDING)
Then only one consumer have to receive message from queue (PENDING)
@Given("one producer, several consumers, one message")
@Pending
public void givenOneProducerSeveralConsumersOneMessage() {
  // PENDING
}

@When("producer sends message to queue")
@Pending
public void whenProducerSendsMessageToQueue() {
  // PENDING
}

@Then("only one consumer have to receive message from queue")
@Pending
public void thenOnlyOneConsumerHaveToReceiveMessageFromQueue() {
  // PENDING
}



Running story PublisherSubscriber.story

(PublisherSubscriber.story)
Scenario: broadcast message receiving by all subscribers of topic
Given one publisher, several subscribers, one message (PENDING)
When publisher sends message (PENDING)
Then all subscribers have to receive this message (PENDING)
@Given("one publisher, several subscribers, one message")
@Pending
public void givenOnePublisherSeveralSubscribersOneMessage() {
  // PENDING
}

@When("publisher sends message")
@Pending
public void whenPublisherSendsMessage() {

Processing system properties {}
Using controls EmbedderControls[batch=false,skip=false,generateViewAfterStories=true,ignoreFailureInStories=true,ignoreFailureInView=true,verboseFailures=false,verboseFiltering=false,storyTimeoutInSecs=60,failOnStoryTimeout=false,threads=1]

(BeforeStories)

Running story ProducerConsumer.story

(ProducerConsumer.story)
Scenario: simple message receiving by one consumer
Given one producer, one consumer, one message (PENDING)
When producer sends message to queue (PENDING)
Then consumer receives message from queue (PENDING)
@Given("one producer, one consumer, one message")
@Pending
public void givenOneProducerOneConsumerOneMessage() {
  // PENDING
}

@When("producer sends message to queue")
@Pending
public void whenProducerSendsMessageToQueue() {
  // PENDING
}

@Then("consumer receives message from queue")
@Pending
public void thenConsumerReceivesMessageFromQueue() {
  // PENDING
}


Scenario: simple message receiving by several consumers:only one of them have to receive message
Given one producer, several consumers, one message (PENDING)
When producer sends message to queue (PENDING)
Then only one consumer have to receive message from queue (PENDING)
@Given("one producer, several consumers, one message")
@Pending
public void givenOneProducerSeveralConsumersOneMessage() {
  // PENDING
}

@When("producer sends message to queue")
@Pending
public void whenProducerSendsMessageToQueue() {
  // PENDING
}

@Then("only one consumer have to receive message from queue")
@Pending
public void thenOnlyOneConsumerHaveToReceiveMessageFromQueue() {
  // PENDING
}



Running story PublisherSubscriber.story

(PublisherSubscriber.story)
Scenario: broadcast message receiving by all subscribers of topic
Given one publisher, several subscribers, one message (PENDING)
When publisher sends message (PENDING)
Then all subscribers have to receive this message (PENDING)
@Given("one publisher, several subscribers, one message")
@Pending
public void givenOnePublisherSeveralSubscribersOneMessage() {
  // PENDING
}

@When("publisher sends message")
@Pending
public void whenPublisherSendsMessage() {
  // PENDING
}

@Then("all subscribers have to receive this message")
@Pending
public void thenAllSubscribersHaveToReceiveThisMessage() {
  // PENDING
}


(AfterStories)  // PENDING
}

@Then("all subscribers have to receive this message")
@Pending
public void thenAllSubscribersHaveToReceiveThisMessage() {
  // PENDING
}


(AfterStories)


 Everything looks good: jbehave found both .story files and even generated for us "mockup"(function definitions) of future implementation!

So, we can copy/paste function definitions to our  test classes in to"fill"(implement) them.

5. Stories implementation.

Now, let's put code into generated function definitions.
 
ProducerConsumerJB

package com.demien.amq.jbehave;

import com.demien.amq.Consumer;
import com.demien.amq.Producer;
import com.demien.amq.TestObject;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.jbehave.core.annotations.Given;
import org.jbehave.core.annotations.Then;
import org.jbehave.core.annotations.When;

import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.*;

/**
 * Created by dmitry on 14.02.15.
 */
public class ProducerConsumerJB {

    public static String brokerURL = "tcp://localhost:61616";
    private final ConnectionFactory factory = new ActiveMQConnectionFactory(brokerURL);
    private final String queueName1="TestQueue1";
    private final String queueName2="TestQueue2";

    Producer<TestObject> producer;
    Consumer<TestObject> consumer;
    List<Consumer<TestObject>> consumers;
/*
    Scenario: simple message receiving by one consumer
    Given one producer, one consumer, one message (PENDING)
    When producer sends message to queue (PENDING)
    Then consumer receives message from queue (PENDING)
*/
    @Given("one producer, one consumer, one message")
    public void givenOneProducerOneConsumerOneMessage() throws JMSException {
        producer=new Producer<TestObject>(factory, queueName1);
        consumer=new Consumer<TestObject>(factory, queueName1);
        consumer.setConsumerId("THE_ONLY_CONSUMER");
    }

    @When("producer sends message to queue")
    public void whenProducerSendsMessageToQueue() throws JMSException, InterruptedException {
        producer.postObjectMessage(new TestObject());
        Thread.sleep(1000);
    }

    @Then("consumer receives message from queue")
    public void thenConsumerReceivesMessageFromQueue() {
        int receivedMessageCount=consumer.getMessages().size();
        assertEquals(1, receivedMessageCount);
    }

/*
    Scenario: simple message receiving by several consumers:only one of them have to receive message
    Given one producer, several consumers, one message (PENDING)
    When producer sends message to queue (PENDING)
    Then only one consumer have to receive message from queue (PENDING)
*/
    @Given("one producer, several consumers, one message")
    public void givenOneProducerSeveralConsumersOneMessage() throws JMSException {
        producer=new Producer<TestObject>(factory, queueName2);
        consumers=new ArrayList<Consumer<TestObject>>();
        for (int i=0;i<10;i++) {
            Consumer<TestObject> eachConsumer=new Consumer<TestObject>(factory, queueName2);
            eachConsumer.setConsumerId("Consumer#"+Integer.toString(i));
            consumers.add(eachConsumer);
        }
    }
/* - already implemented
    @When("producer sends message to queue")
    public void whenProducerSendsMessageToQueue() {
        // PENDING
    }
*/
    @Then("only one consumer have to receive message from queue")
    public void thenOnlyOneConsumerHaveToReceiveMessageFromQueue() {
        int receivedMessageCount=0;
        for (Consumer<TestObject> eachConsumer: consumers) {
            receivedMessageCount=receivedMessageCount+eachConsumer.getMessages().size();
        }
        assertEquals(1, receivedMessageCount);
    }
} 
 
 
PublisherSubscriberJB

package com.demien.amq.jbehave;

import com.demien.amq.Publisher;
import com.demien.amq.Subscriber;
import com.demien.amq.TestObject;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.jbehave.core.annotations.Given;
import org.jbehave.core.annotations.Then;
import org.jbehave.core.annotations.When;

import javax.jms.ConnectionFactory;
import javax.jms.JMSException;
import java.util.ArrayList;
import java.util.List;

import static org.junit.Assert.assertTrue;

/**
 * Created by dmitry on 09.02.15.
 */
public class PublisherSubscriberJB {
    public static String brokerURL = "tcp://localhost:61616";
    private final ConnectionFactory factory = new ActiveMQConnectionFactory(brokerURL);
    private final String topicName="TestTopic";

    private final int SUBSCRIBERS_COUNT=10;
    Long TEST_ID=-1L;
    String TEST_NAME="test name";

    Publisher<TestObject> publisher;
    List<Subscriber<TestObject>> subscribers;

/*    (PublisherSubscriber.story)
    Scenario: broadcast message receiving by all subscribers of topic
    Given one publisher, several subscribers, one message (PENDING)
    When publisher sends message (PENDING)
    Then all subscribers have to receive this message (PENDING)
*/
    @Given("one publisher, several subscribers, one message")
    public void givenOnePublisherSeveralSubscribersOneMessage() throws JMSException {
        publisher=new Publisher<TestObject>(factory, topicName);

        subscribers=new ArrayList<Subscriber<TestObject>>();
        for (int i=0; i<SUBSCRIBERS_COUNT; i++) {
            Subscriber<TestObject> subscriber=new Subscriber<TestObject>(factory, topicName);
            subscriber.setSubscriberId(Integer.toString(i));
            subscribers.add(subscriber);
        }
    }

    @When("publisher sends message")
    public void whenPublisherSendsMessage() throws JMSException, InterruptedException {

        TestObject testObject=new TestObject();
        testObject.id=new Long(TEST_ID);
        testObject.name=new String(TEST_NAME);

        //post message
        publisher.postObjectMessage(testObject);
        Thread.sleep(1000);
    }

    @Then("all subscribers have to receive this message")
    public void thenAllSubscribersHaveToReceiveThisMessage() {
        for (Subscriber<TestObject> subscriber:subscribers) {
            TestObject received=subscriber.getMessages().get(0);
            assertTrue(received.name.equals(TEST_NAME));
            assertTrue(received.id.equals(TEST_ID));
        }
    }

}


6. Final run and results

Now, we can run our message broker and after the we can finally run our JBehave tests .
My results :

Running story ProducerConsumer.story

(ProducerConsumer.story)
Scenario: simple message receiving by one consumer
log4j:WARN No appenders could be found for logger (org.apache.activemq.transport.WireFormatNegotiator).
log4j:WARN Please initialize the log4j system properly.
Given one producer, one consumer, one message
Sending message:com.demien.amq.TestObject@807bbc
Consumer[THE_ONLY_CONSUMER] Message received:
When producer sends message to queue
Then consumer receives message from queue

Scenario: simple message receiving by several consumers:only one of them have to receive message
Given one producer, several consumers, one message
Sending message:com.demien.amq.TestObject@7b0aef
Consumer[Consumer#0] Message received:
When producer sends message to queue
Then only one consumer have to receive message from queue


Running story PublisherSubscriber.story

(PublisherSubscriber.story)
Scenario: broadcast message receiving by all subscribers of topic
Given one publisher, several subscribers, one message
Subscriber[0] received message.
Subscriber[3] received message.
Subscriber[4] received message.
Subscriber[1] received message.
Subscriber[2] received message.
Subscriber[7] received message.
Subscriber[6] received message.
Subscriber[9] received message.
Subscriber[5] received message.
Subscriber[8] received message.
When publisher sends message
Then all subscribers have to receive this message

As we can see, result are very easy for understanding even for users which are not related with development. Also Jbehave can generate very beautiful html tables with results (whey are located in /target/jbehave directory) such as :
 

Story Reports

StoriesScenariosGivenStory ScenariosSteps

NameExcludedTotalSuccessfulPendingFailedExcludedTotalSuccessfulPendingFailedExcludedTotalSuccessfulPendingFailedNot PerformedIgnorableDuration (hh:mm:ss.SSS)View
AfterStories0000000000000000000:00:00.000stats|html|xml |txt
BeforeStories0000000000000000000:00:00.000stats|xml|html |txt
ProducerConsumer0220000000066000000:00:03.000stats|html|xml |txt
PublisherSubscriber0110000000033000000:00:01.000stats|xml|html |txt
40330000000099000000:00:04.000Totals

Full source code could be downloaded from here.