Search

Thursday, 9 May 2013

Java: JBehave integration with GitHub

During BDD engines comparison and comparing with Cucumber I've found out really serious advantage of JBehave in comparison to other similar engines. It's the ability to read data from external sources different from text files in specific locations as well as the ability to customize that. And I made some sample JBehave integration with JIRA. I did that because I had a plans for Sirius platform to store all tests in JIRA and read them from that location. Well, that link is still useful for projects which use JIRA as tracking system.

But for my project it still doesn't work as I need a shared JIRA instance so I can read tests from there. I don't have it yet... and then I found I don't need that, actually. Since I store Sirius project on GitHub it would be reasonable to share test related resources on GitHub as well. But it's not just about sharing but reading. Actually, GitHub contains it's own tracker. So, in this article I'll describe how to read JBehave scenarios stored as the tickets in the GitHub issue tracker.

General Steps

Generally I should do the following steps:

  1. Create GitHub ticket with the test content
  2. Adding GitHub API
  3. Configure JBehave to read data from the GitHub
  4. Create steps implementation for JBehave

Create GitHub ticket with the test content

Issue tracker is available on any repository main page. E.g. for Sirius it's this link. Just for better filtering I've added one extra label named Test so that I can filter out all other issue types.

So, firstly we'll create new issue labelled as Test with the following content:

Given I'm logged into the system 
And I'm on the home page 
When I click on the "About" link 
Then I should see the "About" screen is open
Here is our scenario

Adding GitHub API

GitHub provides REST API interface for programmatic access to GitHub functionality. And there're already existing libraries implementing clients for that. In particular I'll take this GitHub API for accessing test descriptions.

The library can be downloaded by the following link. There's JAR file anclosed for each specific version. For Maven projects it can be included by adding dependency into the pom.xml file:

<dependency>
 <groupId>org.kohsuke</groupId>
 <artifactId>github-api</artifactId>
 <version>1.41</version>
</dependency>
Now we're ready to extend JBehave capabilities.

Configure JBehave to read data from the GitHub

Next steps are pretty similar to the ones described for JBehave integration with JIRA.

Customize JBehave Story Loader

It's all about extension of existing story loader class for JBehave. The code is:

package org.github.test;

import org.jbehave.core.io.StoryLoader;

public class GitHubStoryLoader implements StoryLoader {

    protected String testId = "";

    public GitHubStoryLoader(String id) {
        testId = id;
    }

    public String loadStoryAsText(String issueId) {
        // TODO Add implementation
    }
}
The reading from GitHub ticket by specified ID looks like:
        GitHub client = GitHub.connectUsingPassword(<login>, <password>);
        GHRepository repo = client.getMyself().getRepository(<repository name>);
        
        GHIssue issue = repo.getIssue(testId);
        return issue.getBody();
So, if we put all things altogether we'll get the following:
package org.github.test;

import org.jbehave.core.io.StoryLoader;
import org.kohsuke.github.GHIssue;
import org.kohsuke.github.GHRepository;
import org.kohsuke.github.GitHub;

public class GitHubStoryLoader implements StoryLoader {

    protected String testId = "";

    public GitHubStoryLoader(String id) {
        testId = id;
    }

    public String loadStoryAsText(String issueId) {
        try {
            GitHub client = GitHub.connectUsingPassword(<login>, <password>);
            GHRepository repo = client.getMyself().getRepository(<repository name>);
        
            GHIssue issue = repo.getIssue(new Integer(testId));
            return issue.getBody();
        } 
        catch (Throwable e) {
            ;
        }
        return null;
    }
}

Create sample test class

After that we should initialize test class with new story loader. Just like before our test class looks like:

package org.github.test;

import java.util.LinkedList;
import org.jbehave.core.configuration.Configuration;
import org.jbehave.core.configuration.MostUsefulConfiguration;
import org.jbehave.core.junit.JUnitStory;
import org.jbehave.core.reporters.Format;
import org.jbehave.core.reporters.StoryReporterBuilder;
import org.jbehave.core.steps.InjectableStepsFactory;
import org.jbehave.core.steps.InstanceStepsFactory;

public class JiraTestOperationsTest extends JUnitStory {

 public LinkedList<Object> stepDefinitions = new LinkedList<Object>();

 @Override
 public Configuration configuration() {
  return new MostUsefulConfiguration()
    .useStoryLoader(new GitHubStoryLoader("1"))
    .useStoryReporterBuilder(
      new StoryReporterBuilder()
        .withRelativeDirectory("")
        .withDefaultFormats()
        .withFormats(Format.CONSOLE, Format.TXT,
          Format.XML, Format.HTML));
 }

 @Override
 public InjectableStepsFactory stepsFactory() {
  return new InstanceStepsFactory(configuration(), this.stepDefinitions);
 }

 public DirectoryOperationsTest() {
  this.stepDefinitions.add(new GithubTestOperationsSteps());
 }
}
First highlighted part shows the placeholder where we put the reference to our loader with the parameter corresponding to the issue number we want to get test information from. Second highlighted part shows the place where we include reference to the step bindings. That would be the last class we should implement.

Create steps implementation for JBehave

All that's left is to add step definitions. There's no any specific differences from the example for JIRA, so I'll just put the code:

package org.github.test;

import org.jbehave.core.annotations.Given;
import org.jbehave.core.annotations.Then;
import org.jbehave.core.annotations.When;

public class GithubTestOperationsSteps {

 public GithubTestOperationsSteps() {
  super();
 }

 @Given("I'm logged into the system")
 public void givenImLoggedIntoTheSystem() {
  System.out.println("Output: we're logged into the system");
 }

 @Given("I'm on the home page")
 public void givenImOnTheHomePage() {
  System.out.println("Output: the home page is open");
 }

 @When("I click on the \"About\" link")
 public void whenIClickOnTheAboutLink() {
  System.out.println("Output: the click was done");
 }

 @Then("I should see the \"About\" screen is open")
 public void thenIShouldSeeTheAboutScreenIsOpen() {
  System.out.println("Output: the About screen is open");
 }
}
That's it. We've prepared sample test where the scenario is read from GitHub issue description.

Running test

Now we can run this test. It should produce the output like:

Running story org/github/test/github_test_operations_test.story

(org/github/test/github_test_operations_test.story)
Scenario: 
Output: we're logged into the system
Given I'm logged into the system
Output: the home page is open
And I'm on the home page
Output: the click was done
When I click on the "About" link
Output: the About screen is open
Then I should see the "About" screen is open



(AfterStories)

Generating reports view to 'D:\Work\SiriusDev\Github_TEST\target' using formats '[stats, console, txt, xml, html]' 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 1 stories (of which 0 pending) containing 1 scenarios (of which 0 pending)
As it's seen on the console output all output to the console was produced which means that our test was executed.

Conclusion

That's another example how we can read test scenarios from the external system. Actually, a lot of similar stuff can be written for many other systems. However, the idea remains the same: different people performing different roles in the test automation project use different systems. Such integration makes a bridge between such people distributing work load accross the team. And again, we can add any actions to get some more details about scenario. The main thing is that it's not a part of the tests source code.

P.S.: Maybe somewhere in the future I'll create dedicated library which cover all those integrations because everything is pretty typical.

No comments:

Post a Comment