Java Ninja Chronicles By Norris Shelton

Things I learned in the pursuit of code

As a follow-up to Cucumber Springframework Integration, I needed to get more flexibility to control when Cucumber was ran. With the current configuration, the Cucumber tests ran every time the Maven build ran. This is as designed, but we needed more flexibility. We also needed the ability to run the Cucumber tests, but without building the code again. I wanted to build the code once, then tests it as it was deployed to each environment. I wanted to ensure that the golden build was the only build that I was using. This cuts down on the variables when there are problem. If this code works in DEV, but doesn’t work in the next environment, then it is something different in the environments.

Controlling database connections per environment

We had the problem where we thought that the Cucumber tests were being ran against our integration environment. This didn’t turn out to be true, because the Cucumber tests were using the same datasource as our Junit tests. Those always ran against our development environment. To overcome the datasource issue, I moved from a single datasource to a datasource per environment and using Spring profiles to determine which database connection to use. As example of what I placed in the Springframework configuration is:

    <beans profile="default">
        <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"
              p:driverClassName="com.mysql.jdbc.Driver"
              p:url="jdbc:mysql://dev_dns/my_database?zeroDateTimeBehavior=convertToNull"
              p:username="_username_"
              p:password="_password_"
              p:maxWait="3000"
              p:maxIdle="100"
              p:maxActive="10"/>
    </beans>
    <beans profile="dev">
        <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"
              p:driverClassName="com.mysql.jdbc.Driver"
              p:url="jdbc:mysql://dev_dns/my_database?zeroDateTimeBehavior=convertToNull"
              p:username="_username_"
              p:password="_password_"
              p:maxWait="3000"
              p:maxIdle="100"
              p:maxActive="10"/>
    </beans>
    <beans profile="ite">
        <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"
              p:driverClassName="com.mysql.jdbc.Driver"
              p:url="jdbc:mysql://ite_dns/my_database?zeroDateTimeBehavior=convertToNull"
              p:username="_username_"
              p:password="_password_"
              p:maxWait="3000"
              p:maxIdle="100"
              p:maxActive="10"/>
    </beans>

Activating a Spring profile

Now that you have multiple Spring profiles, you can control which profile is active by passing the following to the JVM running the program. If you don’t specify a profile, then default is assumed.

-Dspring.profiles.active=dev

Moving the Cucumber to it’s own Maven module

Once I had that, I realized that having the Cucumber tests as part of the code module caused a problem. Maven processes the build lifecycle steps in sequence. If I wanted to run the tests, then the code would have to be built. This was exactly what I did not want. To overcome this, I needed to move the Cucumber to it’s own module, then if I ran the tests via Maven, there wouldn’t be any code to compile, because the code would be in another module.

I started with a module named bonus-core. This had all of the business logic and the Junit and Cucumber that was used to tests the business logic. I moved all of the Cucumber specific dependencies, steps and features to a new module named bonus-core-integration. This is what the new module’s pom.xml contained:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <artifactId>bonus</artifactId>
        <groupId>com.cdi.igs</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>bonus-core-integration</artifactId>

    <dependencies>
        <!-- The module containing the code to be tested with Cucumber -->
        <dependency>
            <groupId>com.cdi.igs</groupId>
            <artifactId>bonus-core</artifactId>
            <version>${project.parent.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--Cucumber related-->
        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-jvm-deps</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-spring</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

Once this was done, I could run the bonus-code module and execute the Junit tests, but without the Cucumber tests. Since the Cucumber was not in bonus-code-integration, I could now run just the Cucumber tests, but without building.

Parent pom.xml with and without integration tests

This was closer to what I wanted, but it means that If I run mvn test from the parent pom.xml, the Cucumber tests were always ran. I wanted the flexibility to run the main build without the Cucumber tests. I wanted this because we had a previous project that had so many Cucumber tests, that they took 20 minutes to run. We didn’t have this problem in this project, but I didn’t want to have to scramble if that situation developed. I started with the following modules in the parent pom.xml:

<modules>
    <module>bonus-models</module>
    <module>bonus-core</module>
    <module>bonus-services</module>
    <module>bonus-core-integration</module>
</modules>

I wanted the ability to turn off the execution of the integration modules when needed. I came up with the following:

    <profiles>
        <profile>
            <id>bonus-all</id>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <modules>
                <module>bonus-models</module>
                <module>bonus-core</module>
                <module>bonus-services</module>
                <module>bonus-core-integration</module>
            </modules>
        </profile>
        <profile>
            <id>bonus-no-integration</id>
            <modules>
                <module>bonus-models</module>
                <module>bonus-core</module>
                <module>bonus-services</module>
            </modules>
        </profile>
    </profiles>

This means that if the developer doesn’t do anything, then they will get all of the modules. They can specify the alternative profile to exclude to the integration tests for this project. I named them _project_-all and _project_-no-integration because we also had the case there there was a master pom for this project. If the project was opened from that view, I wanted the ability to turn off the integration for individual projects, not just all of them across all of the projects under the master pom.xml.

The end result

Once all of my changes were completed, I was able to control all of the test execution according to whatever situation I was in at the time.

  • All tests – I could run the Junit tests and the integration tests simply by running mvn clean test from the parent pom.xml
  • Only Junit tests – I could run the Junit tests and skip all the integration tests by running mvn clean test -Pbonus-no-integration from the parent pom.xml
  • Only Integration tests – I could run the integration tests and skip the build and the Junit tests by running mvn clean test from within the bonus-core-integration module instead of the parent module. If this is used, then the code would be retrieved from our local Artifactory.

We had a need to have a Cucumber example that tested several different steps in one pass. We then needed to make several different passes across the tests with different data elements. We had been using Examples before, but this took it to a whole new level. We defined a datatable for each step with variables that then were retrieved from a main examples datatable that had all of the data for all of the steps for this run.

The feature files looks like:

Feature: Logging in to play for real and fun money and retrieve player wallet

  @2015r16.3 @US5064
  Scenario Outline: Full register player can launch a casino game to play for real or fun money. In this scenario in the
  back end the player launch a game for real or fun (demo) the we auth the player and retrieve the wallet

    Given I am a fully registered user
    And I am authenticated
    Then I want to create an ACH account at the time of deposit with following details:
      | Routing Number             | <routing_number>   |
      | Account Nickname           | <account_nickname> |
      | Deposit Amount             | <deposit_amount>   |
      | Account Type               | <account_type>     |
      | Agree Terms And Conditions | <agree_t_and_c>    |
    Then my account balance is correct
      | Cash Balance | <deposit_amount> |
    When I get a game launch Url
      | External Game ID | <game_id_external> |
      | Aggregator ID    | <id_aggregator>    |
      | Game Mode        | <game_mode>        |
    And I am authenticated via the remote game server
      | Authorization Status | <auth_status> |
    Then the game should retrieve the wallet and verify the cash balance
      | Cash Balance | <deposit_amount> |
    Then I play the game
      | First Wager Amount        | <firstWagerAmount>       |
      | First Win Amount          | <firstWinAmount>         |
      | First Ending Cash Balance | <firstEndingCashBalance> |
    Then I play the game
      | Second Wager Amount        | <secondWagerAmount>       |
      | Second Win Amount          | <secondWinAmount>         |
      | Second Ending Cash Balance | <secondEndingCashBalance> |
    Then I cleanup the gaming records

    Examples:
      | routing_number | account_nickname | deposit_amount | account_type | agree_t_and_c | game_id_external | id_aggregator | game_mode | auth_status | firstWagerAmount | firstWinAmount | firstEndingCashBalance |
      | 011000015      | MyNewACHAccount  | 95             | chk          | true          | aris50Dragons    | gsi           | DEMO      | SUCCESS     | 10               | 0              | 85                     |
      | 011000015      | MyNewACHAccount  | 95             | chk          | true          | aris50Dragons    | gsi           | REAL      | SUCCESS     | 10               | 0              | 85                     |
      | 011000015      | MyNewACHAccount  | 95             | chk          | true          | aris50Lions      | gsi           | REAL      | SUCCESS     | 10               | 0              | 85                     |
      | 011000015      | MyNewACHAccount  | 95             | chk          | true          | aris50Lions      | gsi           | DEMO      | SUCCESS     | 10               | 0              | 85                     |

Let’s look at the step

Then I want to create an ACH account at the time of deposit with following details:

The datatable contains the following data. Think of the elements on the left as the keys in a map and the elements on the right as the corresponding values.

      | Routing Number             | <routing_number>   |
      | Account Nickname           | <account_nickname> |
      | Deposit Amount             | <deposit_amount>   |
      | Account Type               | <account_type>     |
      | Agree Terms And Conditions | <agree_t_and_c>    |

In this case, the corresponding values are within < and >. This tells Cucumber to look up the actual value in the Examples: datatable.

    Examples:
      | routing_number | account_nickname | deposit_amount | account_type | agree_t_and_c | game_id_external | id_aggregator | game_mode | auth_status | firstWagerAmount | firstWinAmount | firstEndingCashBalance |
      | 011000015      | MyNewACHAccount  | 95             | chk          | true          | aris50Dragons    | gsi           | DEMO      | SUCCESS     | 10               | 0              | 85                     |
      | 011000015      | MyNewACHAccount  | 95             | chk          | true          | aris50Dragons    | gsi           | REAL      | SUCCESS     | 10               | 0              | 85                     |
      | 011000015      | MyNewACHAccount  | 95             | chk          | true          | aris50Lions      | gsi           | REAL      | SUCCESS     | 10               | 0              | 85                     |
      | 011000015      | MyNewACHAccount  | 95             | chk          | true          | aris50Lions      | gsi           | DEMO      | SUCCESS     | 10               | 0              | 85                     |

For our examples test, we are going to send a map containing the following key/value data:

  • Routing Number -> 011000015
  • Account Nickname -> MyNewACHAccount
  • Deposit Amount -> 95
  • Account Type -> chk
  • Agree Terms and Conditions -> true

Now for the nitty gritty. This is what the step definition looks like:

    @Given("^I want to create an ACH account at the time of deposit with following details:$")
    public void I_want_to_create_an_ACH_account_at_the_time_of_deposit_with_following_details(Map<String, String> tableData) throws Throwable {
        assertTrue(tableData.containsKey("Routing Number"));
        assertTrue(tableData.containsKey("Account Nickname"));
        assertTrue(tableData.containsKey("Deposit Amount"));
        assertTrue(tableData.containsKey("Account Type"));
        assertTrue(tableData.containsKey("Agree Terms And Conditions"));


        achCashierModel.setRoute(tableData.get("Routing Number"));
        achCashierModel.setLabel(tableData.get("Account Nickname"));
        achCashierModel.setAcceptedTerms("true".equals(tableData.get("Agree Terms And Conditions")));
        achCashierModel.setIdAchType(tableData.get("Account Type"));
        achCashierModel.setTransactionAmountString(tableData.get("Deposit Amount"));

        // do some testing stuff
    }

The method takes Map tableData as the method parameter. From here on, you can access it like a regular map. In this case, the first thing the step does is verify that the correct input values were specified. To use them, a normal map.get() is called.

That’s for a single pass through the tests. Notice, that in our Examples: table, we have four rows of data. What this means is that there are 4 whole tests of data. In my feature file in my IDE, the rows of data are on lines 37, 38, 39 and 40. Here is the kind of data is produced.

Feature: CasinoGame: 28 total, 28 passed 51.51 s
    Feature: Logging in to play for real and fun money and retrieve player wallet
        Scenario Outline: Full register player can launch a casino game to play for real or fun money. In this scenario in the
            Examples:
                Scenario: Line: 37
                    Given I am a fully registered user -- passed
                    And I am authenticated -- passed
                    Then I want to create an ACH account at the time of deposit with the following details: -- passed
                    Then my account balance is correct -- passed
                    When I get a game launch Url -- passed
                    And I am authenticated via the remote game server -- passed
                    Then the game should retrieve the wallet and verify the cash balance -- passed
                Scenario: Line: 38
                    Given I am a fully registered user -- passed
                    And I am authenticated -- passed
                    Then I want to create an ACH account at the time of deposit with the following details: -- passed
                    Then my account balance is correct -- passed
                    When I get a game launch Url -- passed
                    And I am authenticated via the remote game server -- passed
                    Then the game should retrieve the wallet and verify the cash balance -- passed
                Scenario: Line: 39
                    Given I am a fully registered user -- passed
                    And I am authenticated -- passed
                    Then I want to create an ACH account at the time of deposit with the following details: -- passed
                    Then my account balance is correct -- passed
                    When I get a game launch Url -- passed
                    And I am authenticated via the remote game server -- passed
                    Then the game should retrieve the wallet and verify the cash balance -- passed
                Scenario: Line: 40
                    Given I am a fully registered user -- passed
                    And I am authenticated -- passed
                    Then I want to create an ACH account at the time of deposit with the following details: -- passed
                    Then my account balance is correct -- passed
                    When I get a game launch Url -- passed
                    And I am authenticated via the remote game server -- passed
                    Then the game should retrieve the wallet and verify the cash balance -- passed


August 21st, 2015

Posted In: Cucumber, Java, java ninja, Javaninja

Tags:

Leave a Comment

I have a normal Springframework-enabled project arranged as a Maven project. There were already JUnit tests in the project, along with the requisite Spring configuration.

I needed to add Cucumber tests to the project. I started by adding a cucumber directory to my src/test/java and src/test/resources directories.

I added my feature file in ../src/test/resources/com/cdi/igs/adapter/cucumber/features/Login.feature.

Feature: Login

  @2014w34 @US2422
  Scenario Outline: A lite registered player with email verified should be able submit username and password in the poker client.
  After submission player will see a system message to complete registration and will get re-direct to complete registration step 2 in IGP

    Given my username is "<UserName>"
    And my password is "<Password>"
    And my registration status is "lit"
    And my email verification status is verified as "<email verification status>"
    When I provide my credentials
    Then I should receive the following Poker status "<login result>"
  Examples:
    | UserName | Password   | login result            |email verification status|
    | player1  | Password#1 | AUTH_FAILED_LIT         | 1                       |
  @2014w34 @US2326
  Scenario Outline: A fully registered user should be able to login into the existing poker client. User in examples must be a fully registered user. 0 = SUCCESS, 1 = AUTH_FAILED

    Given my username is "<UserName>"
    And my password is "<Password>"
    And my registration status is "ful"
    When I provide my credentials
    Then I should receive the following Poker status "<Login Result>"
    And I should receive the following demographic data "<First Name>" "<Last Name>" "<Address1>" "<Address2>" "<City>" "<State>" "<Zip>"
    And I should have a third party authentication token

  Examples:
    | UserName | Password   | Login Result | First Name | Last Name | Address1          | Address2 | City     | State | Zip   |
    | player2  | Password#1 | SUCCESS      | Player     | Test      | 1505 Test Address | Apt 100  | New York | NY    | 80000 |


  @2014w34 @US2538
  Scenario Outline: A lite registered user  whose email is not verified will be shown the relevant messaging, and they will be requested to become fully registered
    Given my username is "<UserName>"
    And my password is "<Password>"
    And my registration status is "lit"
    And my email verification status is verified as "<email verification status>"
    When I provide my credentials
    Then I should receive the following Poker status "<login result>"

  Examples:
    | UserName    | Password        |email verification status  | login result             |
    | 8player     | Password!#01    |0                          | AUTH_FAILED_LIT_NO_EMAIL |


  @2014w36 @US2639
  Scenario Outline: Player with existing Password locked status with IGP authentication, cannot login to poker client.

    Given my username is "<UserName>"
    And my password is "<Password>"
    And my person status is "psl"
    When I provide my credentials
    Then I should receive the following Poker status "<login result>"

  Examples:
    | UserName | Password     | login result              |
    | gawky1  | Password#1    | LOCKED_PASSWORD_RESET     |

  @2014w36 @US2629
  Scenario Outline: Player with existing Reset locked status with IGP authentication, cannot login to poker client.

    Given my username is "<UserName>"
    And my password is "<Password>"
    And my person status is "rsl"
    When I provide my credentials
    Then I should receive the following Poker status "<login result>"

  Examples:
    | UserName  | Password      | login result                  |
    | gawky     | Password#1    | LOCKED_CUSTOMER_SERVICE_RESET |

I added my steps file that defines the implementations in …/src/test/java/com/cdi/igs/adapter/cucumber/steps/LoginSteps.java.

package com.cdi.igs.adapter.cucumber.steps;

import com.cdi.igs.adapter.login.LoginRequest;
import com.cdi.igs.adapter.login.LoginResponse;
import com.cdi.igs.adapter.poker.login.PokerLoginService;
import com.cdi.igs.dao.person.Person;
import com.cdi.igs.dao.person.PersonRepository;
import com.cdi.igs.dao.personhasverifiableitem.PersonHasVerifiableItem;
import com.cdi.igs.dao.personhasverifiableitem.PersonHasVerifiableItemRepository;
import cucumber.api.java.en.And;
import cucumber.api.java.en.Given;
import cucumber.api.java.en.Then;
import cucumber.api.java.en.When;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;

import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;

/**
 * Login cucumber steps.
 * @author norris.shelton
 */
@ContextConfiguration("classpath:TestCucumber-context.xml")
public class LoginSteps {
    @Autowired
    private PersonRepository        personRepository;
    @Autowired
    private PersonHasVerifiableItemRepository personHasVerifiableItemRepository;
    @Autowired
    private PokerLoginService       pokerLoginService;


    private String     username;
    private String     password;
    private Person     person;
    LoginResponse loginResponse;

    @Given("^my username is "([^"]*)"$")
    public void my_username_is(String username) throws Throwable {
        this.username = username;
    }


    @And("^my password is "([^"]*)"$")
    public void my_password_is(String password) throws Throwable {
        this.password = password;
    }

    @Given("^my registration status is "(.*?)"$")
    public void my_registration_status_is(String registrationStatus) throws Throwable {
        person = personRepository.findByUserName(username);
        assertEquals(registrationStatus, person.getIdRegistrationStatus());
    }



    @Then("^I should receive the following Poker status "(.*?)"$")
    public void i_should_receive_the_following_Poker_status(String responseStatus) throws Throwable {
        assertEquals(responseStatus, loginResponse.getStatus().value());
    }

    @Given("^my email verification status is verified as "(.*?)"$")
    public void my_email_verification_status_is_verified_as(String emailVerificationStatus) throws Throwable {
     List<PersonHasVerifiableItem> personHasVerifiableItemList =
             personHasVerifiableItemRepository.findByIdPersonAndIdVerifiableItemOrderByTimeStampDesc(person.getIdPerson(), "ema");
        if (!personHasVerifiableItemList.isEmpty()) {
            for (PersonHasVerifiableItem emailVerifiableItem : personHasVerifiableItemList) {
                if (emailVerifiableItem.isSuccess()) {
                    assertEquals(person.getIdPerson(),emailVerifiableItem.getIdPerson());
                    break;
                }
            }
        }
    }

    @Given("^my person status is "(.*?)"$")
    public void my_person_status_is(String personStatus) throws Throwable {
        person = personRepository.findByUserName(username);
        assertEquals(personStatus, person.getIdPersonStatus());
    }

    @When("^I provide my credentials$")
    public void I_provide_my_credentials() throws Throwable {
        LoginRequest loginRequest = new LoginRequest();
        loginRequest.setUserId(username);
        loginRequest.setPassword(password);
        loginResponse = pokerLoginService.login(loginRequest);
    }

    @And(
        "^I should receive the following demographic data "([^"]*)" "([^"]*)" "([^"]*)" "([^"]*)" "" +
        "([^"]*)" "([^"]*)" "([^"]*)"$")
    public void I_should_receive_the_following_demographic_data(String firstName,
                                                                String lastName,
                                                                String address1,
                                                                String address2,
                                                                String city,
                                                                String state,
                                                                String zip)
    throws Throwable {
        assertEquals(firstName, StringUtils.trimToEmpty(loginResponse.getFirstName()));
        assertEquals(lastName, StringUtils.trimToEmpty(loginResponse.getLastName()));
        assertEquals(address1, StringUtils.trimToEmpty(loginResponse.getStreetAddress1()));
        assertEquals(address2, StringUtils.trimToEmpty(loginResponse.getStreetAddress2()));
        assertEquals(city, StringUtils.trimToEmpty(loginResponse.getCity()));
        assertEquals(state, StringUtils.trimToEmpty(loginResponse.getIdState()));
        assertEquals(zip, StringUtils.trimToEmpty(loginResponse.getZipCode()));
    }

    @And("^I should have a third party authentication token$")
    public void I_should_have_a_third_party_authentication_token() throws Throwable {
        assertNotNull(StringUtils.trimToNull(loginResponse.getThirdPartyAuthenticationToken()));
    }
}

Now that I have the feature and the steps created, I added in the Cucumber runner in
…/src/test/java/com/cdi/igs/adapter/cucumber/TestCucumber.java. Notice the glue property of @CucumberOptions. That defines directories to scan.

package com.cdi.igs.adapter.cucumber;</code>

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

/**
* JUnit test class for cucumber.
* @author norris.shelton
*/
@RunWith(Cucumber.class)
@CucumberOptions(
tags = {},
glue = {"com.cdi.igs.adapter.cucumber", "cucumber.api.spring"})
public class TestCucumber {
}

In the top of the LoginSteps class, there was a ContextConfiguration annotation.

@ContextConfiguration("classpath:TestCucumber-context.xml")

That Spring context config file is located in …/src/test/resources/TestCucumber-context.xml and thee contents of that are as follows:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans   http://www.springframework.org/schema/beans/spring-beans.xsd">

    <import resource="classpath*:TestAdapter-context.xml"/>
</beans>

It merely says use whatever Spring configuration has already been defined for the rest of the project.

November 20th, 2014

Posted In: Cucumber, Integration Tests, Java, JUnit, Spring, TDD, Test Driven Development, Unit Tests

Tags: , , , , , , , , ,

8 Comments

LinkedIn Auto Publish Powered By : XYZScripts.com