BDD: Cucumber + TestComplete

Files

Author: SmartBear Software
Applies to: TestComplete 11

Cucumber is a Java tool for behavior-driven development (BDD). This article explains how to use TestComplete from your Cucumber tests. If you are unfamiliar with BDD, we recommend that you read the first article of this series - BDD Testing With TestComplete.

You can download the sample code mentioned in this article using the links at the top-right of this page.

In This Article


A TestComplete Class for Cucumber

In BDD tests, you use feature definitions that describe the actions a test should perform and write step definition code to perform these actions. One of the ways to perform test actions is to use an automated testing tool like TestComplete that can easily simulate user actions like selecting menu items or text input.

To help you use TestComplete from Cucumber tests, SmartBear created a special Java library. It allows you to call TestComplete scripts from Java code of Cucumber’s step definitions. You can download it by using a link at the top right of the page.

The library automates TestComplete via COM. It exposes the RunRoutine function that you can call from your step definitions written in Java.

Currently, the library lets you run TestComplete test scripts only. If you need to run a keyword test, or a network suite, do this from the script code that is called by the RunRoutine function.

If you have several TestComplete versions installed, the library will work with the version you installed last.

To learn how the library works, examine its source code that you can download using the link in the top right corner of the page.

The information below is a step-by-step explanation of how to create Cucumber tests and call TestComplete routines.

Configuring the Environment

  1. Install Java Platform (JDK) on your computer. You can download the installation package from this page:
    http://www.oracle.com/technetwork/java/javase/downloads/index.html

  2. Install IntelliJ IDEA. We will use this IDE to set up a test project and run tests. You can download it from this web site:
    https://www.jetbrains.com/idea/download/

  3. Download the TestComplete library (TestComplete.jar) by using the link at the top right of the page. Remember where you have saved the library, because you will have to find it later.

  4. Download the latest version of Maven. It is useful tool for managing project dependencies:
    http://maven.apache.org/download.cgi
    Do not forget to extract the files from the downloaded package.

  5. Set Maven’s environment variables:

    • In the operating system’s Start menu, right-click Computer (or My Computer) and select Properties from the context menu.

    • Click Advanced system settings in the left part of the window.

    • Open the Advanced tab and click Environment Variables.

    • In the System variables group, create two new variables with the following properties (in our example, C:\apache-maven-3.2.5 is the path to Maven installed on the computer):

       
      Name Value
      M2_Home C:\apache-maven-3.2.5
      M2 C:\apache-maven-3.2.5\bin
  6. Open IntelliJ IDEA. On the Welcome screen, select Configure | Settings.

  7. Choose Build, Execution, Deployment | Build Tools | Maven and make sure that the Maven home directory field contains the path to your Maven folder.

  8. Select Build, Execution, Deployment | Build Tools | Maven | Importing and enable the Import Maven projects automatically option. It allows Maven to download necessary libraries automatically.

  9. Select Plugins in the left part of the page and click Browse repositories.

  10. Find the Cucumber for Java plugin and install it.

  11. After the plugin is installed, click Restart ItelliJ IDEA. This will save the changes automatically and restart the IDE.

Preparing a TestComplete Project

  1. Create test scripts to be called from Cucumber tests. Open your TestComplete project in TestComplete and record or write script code to be used in Cucumber tests. Below are links to some TestComplete help topics explaining how to perform frequent tasks in your tests:

  2. You can download a sample TestComplete project using the link at the top of the page.

  3. Modify your TestComplete project’s properties:

    • Click Tools | Current Project Properties to open the project’s properties.

    • Select Playback in the list in the left part of the page and clear the Stop on error check box:

Creating a Cucumber Project

To create a Cucumber test, we will use IntelliJ IDEA. It allows you to easily connect all the needed libraries, customize the project and create feature and step definitions.

Preparing an IDEA Project

1. Creating a Project

  1. Launch IntelliJ IDEA.

  2. On the Welcome screen, click Create New Project.

  3. Select Maven in the left part of the dialog and make sure that the desired JDK version is specified in the Project SDK field. If needed, click New and specify the path to the JDK installed on your computer.

  4. Enable the Create from archetype option and select the org.apache.maven.archetypes:maven-archetype-quickstart archetype. Click Next.

  5. Specify the desired settings on the next pages of the New Project wizard.

2. Specifying the Required Dependencies.

  1. Open the pom.xml file. Find the dependencies element that Maven specifies automatically. It looks like the following code snippet:

      <dependencies>
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>3.8.1</version>
          <scope>test</scope>
        </dependency>
      </dependencies>

     

  2. Replace the dependencies element with the following:

      <dependencies>
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.12</version>
          <scope>test</scope>
        </dependency>
        <dependency>
          <groupId>info.cukes</groupId>
          <artifactId>cucumber-junit</artifactId>
          <version>1.2.2</version>
        </dependency>
        <dependency>
          <groupId>info.cukes</groupId>
          <artifactId>cucumber-java</artifactId>
          <version>1.2.2</version>
        </dependency>
        <dependency>
          <groupId>net.sf.jacob-project</groupId>
          <artifactId>jacob</artifactId>
          <version>1.14.3</version>
        </dependency>
      </dependencies>

     

  3. Select File | Project Structure in the main menu to open the Project Structure dialog.

  4. Select the Libraries  category on the left of the dialog and choose a JACOB library from the list (for example, Maven: net.sf.jacob-project:jacob:1.14.3):

  5. Click  Add in the right pane and select any JACOB dll to specify Native Library Locations. Usually, you can find a JACOB dll in the JACOB directory of the Maven’s repository. IntelliJ IDEA offers you this directory by default. Do not close the dialog, you will use it in the next step.

3. Adding the TestComplete Library

  1. In the Libraries category of the Project Structure dialog, click  New Project Library | Java and specify the path to the TestComplete library (the TestComplete.jar file that you downloaded earlier).

  2. Click OK to close the dialog.

4. Customizing the Project

Make sure the name of the package matches the directory structure of your project.

Also, pay attention to the string that specifies the path to the feature definitions (in our example, src/test/resources). You will create this folder in the next step.

  1. Open IntelliJ IDEA’s Project panel. It shows the file structure of the project. If it is hidden, select View | Tool Window | Project from the main menu.

  2. Find the directory that contains the AppTest.java file (for example, Cucumber Sample Project/src/test/java/com.example.testingNotepad). This file was created by Maven automatically. Right-click the file and select Delete from the context menu.

  3. Right-click the directory where the AppTest.java file was located and select New | Java Class from the context menu. Name the class, for example, TestRunner. You will use this class to run Cucumber tests.

  4. Add the following code to the class you have just created:

    package com.example.testingNotepad;

    import cucumber.api.junit.*;
    import org.junit.runner.RunWith;
    import cucumber.api.CucumberOptions;

    @RunWith(Cucumber.class)
    @CucumberOptions(
            features={"src/test/resources"}
    )
    public class TestRunner {}

     

  5. Right-click the test directory (src/test) and select New | Directory. Enter resources as the name of the new directory. It will contain the feature definitions of your Cucumber test.

Writing Feature Definitions

When you specify feature definitions, you use a specific pattern, the so-called Gherkin syntax. You can find a detailed description of it and an explanation of how to create feature definitions in the previous article of this series: BDD Testing With TestComplete.

To create a feature definition for Cucumber, do the following:

  1. In IntelliJ IDEA’s Project panel, right-click the folder containing your feature definitions (Cucumber Sample Project/src/test/resources in our example) and select New | File. Specify the desired name of the file with the .feature extension (for example, typing.feature).

  2. Specify the desired feature and testing scenarios. Below is a feature definition and two scenarios we will use in this tutorial:

    Feature: Typing text
      In order to see what I type
      As a user
      I want Notepad to show the typed text

      Scenario: Typing text
        Given I open Notepad
        When I type "Line One"
        Then I see "Line One" in the Notepad window

      Scenario: Typing two lines of text
        Given I open Notepad
        When I type "Line One"
        And I start a new line
        And I type "Line Two"
        Then I see "Line One" and "Line Two" on separate lines

     

Defining Steps

Step definitions specify the actions to be performed when running a test scenario.

Cucumber is a Java tool. To create step definitions, you create classes in your project and mark them with the specified annotations.

Creating a step definition includes the following steps:

  1. Creating a step definition file.

  2. Importing the required libraries.

  3. Specifying TestComplete parameters.

  4. Defining steps.

1. Creating a Step Definition File

  1. In IntelliJ IDEA’s Project panel, find the test folder of the desired package (in our example, Cucumber Sample Project/src/test/java/com.example.testingNotepad).

  2. Right-click the folder and select New | Java class from the context menu.

  3. Specify the desired name for the class (for example, steps).

  4. Make sure the name of the package matches the directory structure of your project.

2. Importing the Required Libraries

In order for Java classes to be able to use Cucumber, you need to import some libraries to your step definition file. To do this, add the following lines to the header of each Java file that contains the appropriate step definitions:

// Your Java file

import cucumber.api.java.After;
import cucumber.api.java.Before;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;
import cucumber.api.Scenario;
import com.smartbear.cucumber.TestComplete;

3. Specifying TestComplete Parameters

To call a TestComplete test script, Cucumber should know the name of your TestComplete project, the script unit name, the script routine to be called, and the parameters of the call. To specify these data, you create a separate class that will contain the needed values.

In your step definition file, create the following class:

class World {
  public static TestComplete tc;

  public static TestComplete TC() {
    if (tc == null) {

      // Create an instance of the TestComplete object
      tc = new TestComplete(
        // The path to the TestComplete suite
        "C:\\Work\\Tests\\TestComplete Project - TestingNotepad\\TestingNotepad.pjs",
        // The name of the test project
        "TestingNotepad",
        // The name of the log
        "TestingNotepad Log"
      );
      Runtime.getRuntime().addShutdownHook(new Thread() {
        public void run() {
          try {

            // Export the log
            World.tc.ExportLog();
            // Stop playback after the test finishes (excluded in this example)
            World.tc.GetIntegration().Stop();
            // Close TestComplete after the test finishes
            World.tc.Quit();

          } catch (Throwable e) {
            e.printStackTrace();
          }
        }
      });
    }
    return tc;
  }
}

The constructor of the TestComplete class requires three string parameters:

    tc = new TestComplete(PathToProjectSuite, ProjectName, LogName);

  • PathToProjectSuite – The fully-qualified name of the TestComplete project suite file.
  • ProjectName –The name of your TestComplete project (you should specify the name as it is shown in TestComplete’s Project Explorer panel).
  • LogName - A string parameter that specifies the name of the test log item that will store the test results. This name will also be used for the test log’s file.

The run method of the World class calls three methods:

  • World.tc.ExportLog() - Commands TestComplete to export the test log to a .mht file at the end of the run.
  • World.tc.GetIntegration().Stop() - Stops the test playback after the test finishes.
  • World.tc.Quit() - Closes TestComplete when the test run is over. It stops the test playback if you comment out the previous line.

If you want to omit some step, just comment it out. For example if you want TestComplete to keep running after the test finishes, add // at the beginning of the corresponding line:

World.tc.ExportLog();
World.tc.GetIntegration().Stop();
// World.tc.Quit(); <- this line will not execute

Note: If you comment out both the World.tc.GetIntegration().Stop() and the World.tc.Quit() commands, the test playback will continue. In this case, you will have to stop it manually.

4. Defining Steps

As you remember, test scenarios are written in the Given-When-Then format, for example:

  Scenario: Typing text
    Given I open Notepad
    When I type "Line One"
    Then I see "Line One" in the Notepad window

When you run a test, Cucumber parses a scenario definition and runs a step definition for each Given, When and Then test steps. That is, you have to create a step definition for each of these lines. If some step has several parts conjunct with the AND keyword, then you should create a step definition code for each AND line.

Step definitions must be visible to Cucumber, so you must place them in any public class of your project. In our example, we place them in the Steps class of the created Steps.java file.

A step definition is a Java method with an annotation that corresponds to a keyword, for example --

@Given("^I open Notepad$")
public void open_notepad() throws Throwable
{
  World.TC().RunRoutine("Unit1.openNotepad");
}

Let’s examine this line by line:

  • The annotation links the step definition with the test scenario. For example, the annotation for the following test step --

    Given I open Notepad

    -- looks like the following sample line:

    @Given("^I open Notepad$")

    As you can see, the annotation includes a keyword (Given), and receives a parameter that is a regular expression matching the desired step’s description.

  • The next line is a declaration of the step’s method. In Cucumber, a method for step definitions must be public and must not return any result. Also, the method can throw exceptions. So, its declaration looks as follows:

    public void open_notepad() throws Throwable

  • The method declaration is followed by the method body. In our example, this is a single line of code that calls the RunRoutine method of the TestComplete class that we created earlier:

    {
      World.TC().RunRoutine("Unit1.openNotepad");
    }

    In our case, the RunRoutine function receives only one parameter – a string that specifies the name of the TestComplete script routine to be called. The name is specified in the UnitName.RoutineName format.
    If the called script routine uses parameters, you specify them in RunRoutine after the routine name:

    World.TC().RunRoutine("myUnitName.myFunctionName", param1, param2, ..., paramN)

Using Parameters

You may want to pass some part of the test scenario definition to the step function as a parameter. For example, a scenario definition can include a string value to be entered in the tested application, or the name of the file to be opened.

In scenario definitions, it is convenient to enclose these values in quotes:

  Scenario: Typing text
    Given I open Notepad
    When I type "Line One"
    Then I see "Line One" in the Notepad window

To extract the value from the step definition, you specify a subexpression that matches the desired parameter and add a parameter to the step’s Java method:

@When("^I type \"(.*)\"$")
public void type_text(String text) throws Throwable
{
  World.TC().RunRoutine("Unit1.typeText", text);
}

In the example above, we use the subexpression (.*) to extract the quoted text from the definition string and to pass the extracted text as the text parameter to the TestComplete script routine.

It is also possible to use multiple parameters. For instance, for the Then line of the following scenario --

Scenario: Typing two lines of text
    Given I open Notepad
    When I type "Line One"
    And I start new line
    And I type "Line Two"
    Then I see "Line One" and "Line Two" on separate lines

-- you can create the following code:

@Then("^I see \"(.*)\" and \"(.*)\" on separate lines$")
public void check_two_lines(String expected_1, String expected_2) throws Throwable
{
World.TC().RunRoutine("Unit1.checkTwoLines", expected_1, expected_2);
}

As you can see, the value extracted by the first subexpression will be passed to the test script as the parameter expected_1, and the value that is extracted by the second subexpression will be passed as expected_2.

5. Defining Scenario Hooks

Besides defining step methods, you must also define two scenario hooks. Cucumber runs the corresponding hook before and after test scenario runs. Working with TestComplete, we use them to place the logs of each test scenario in separate log folders. To do this:

  1. Create the before hook by using the @Before annotation:

    @Before
    public void before(Scenario scenario)
    {
      World.TC().GetLog().CreateFolder(scenario.getName());
    }

    This function creates a folder in the TestComplete test log and gives it the name of the running scenario.

  2. Create the after hook by using the @After annotation:

    @After
    public void after()
    {
      World.TC().GetLog().PopFolder();
    }

    It closes the folder, which the Before hook created, after the test scenario finishes.

Complete Sample Code

Below is the entire code of Cucumber step definitions that we created for our feature description file:

package com.example.testingNotepad;

import cucumber.api.java.After;
import cucumber.api.java.Before;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;
import cucumber.api.Scenario;
import com.smartbear.testcomplete.cucumber;

class World {
  public static TestComplete tc;

  public static TestComplete TC() {
    if (tc == null) {

      tc = new TestComplete(
        // The path to the TestComplete suite
        "C:\\Work\\Tests\\TestComplete Project - TestingNotepad\\TestingNotepad.pjs",
        // The name of the test project
        "TestingNotepad",
        // The name of the log
        "TestingNotepad Log"
      );
      Runtime.getRuntime().addShutdownHook(new Thread() {
        public void run() {
          try {

            // Export the log
            World.tc.ExportLog();
            // Stop playback after the test finishes (excluded in this example)
            World.tc.GetIntegration().Stop();
            // Close TestComplete after the test finishes
            // World.tc.Quit();

          } catch (Throwable e) {
            e.printStackTrace();
          }
        }
      });
    }
    return tc;
  }
}

public class Steps {
  @Given("^I open Notepad$")
  public void open_notepad() throws Throwable
  {
    World.TC().RunRoutine("Unit1.openNotepad");
  }

  @When("^I type \"(.*)\"$")
  public void type_text(String text) throws Throwable
  {
    World.TC().RunRoutine("Unit1.typeText", text);
  }

  @Then("^I see \"(.*)\" in the Notepad window$")
  public void check_lines(String expected) throws Throwable {
    World.TC().RunRoutine("Unit1.checkText", expected);
  }

  @When("^I start a new line$")
  public void new_line() throws Throwable
  {
    World.TC().RunRoutine("Unit1.addNewLine");
  }

  @Then("^I see \"(.*)\" and \"(.*)\" on separate lines$")
  public void check_two_lines(String expected_1, String expected_2) throws Throwable
  {
    World.TC().RunRoutine("Unit1.checkTwoLines", expected_1, expected_2);
  }

  @Before
  public void before(Scenario scenario)
  {
    World.TC().GetLog().CreateFolder(scenario.getName());
  }

  @After
  public void after()
  {
    World.TC().GetLog().PopFolder();
  }
}

Run Cucumber Tests

To run your test, right-click the TestRunner class in IntelliJ IDEA’s Project panel and select Run 'TestRunner'.

You can view the test log in TestComplete (in our example, it continues running after the test is over). Also, the test results will be exported to a .mht file that you can find in the folder where your TestComplete project suite is located.

Conclusion

Behavior-driven development is gaining in popularity nowadays. Using TestComplete, you can improve your BDD tests and create better software products.

If you do not have TestComplete yet, download and try it for free.