Continuous Delivery (CD) Driven Spring Boot Application Development Part 2 – FX Rates Service

In the first part of this post series, we setup the Jenkins project and some initial codes generated from the Spring CLI. The project we are working on using Spring Boot is a financial application related to the FX market. In this post, we will develop a FX rates service provides FX rates to web clients.

FX Rates Service

In the FX market, when we want to exchange our money to any other currency, normally we will look for a current FX rates of that currency in the market; the rates might be provided by banks or other kinds of financial institutions. Let’s say we have 100 THB, if we want to exchange it to USD, we have to look for a current sell rate of USD/THB. On the other hand if we want to exchange that money in USD back to THB, we have to look for a current buy rate of USD/THB.

The FX Rates service we are developing will provide these buy and sell rates via a REST API and we will show these rates in our web GUI. For the first version, it will display snapshot rates which users have to refresh the rates on a screen by themselves using the Refresh button on a rate panel as the screenshot shown below.

FX Rates Panel

FX Rates Panel

The web GUI will be based on Angular Material Design module and fetch FX rates from the REST API using Ajax requests. Let’s start by define the REST API specification first using Swagger Editor (http://editor.swagger.io/).

REST API Specification

The code snippet below is the API specification for the FX Rates service. It produces JSON in respond to given parameters; a currency pair and a rate type. The full specification can be found in the source code shared on the GitHub.

swagger: '2.0'
info:
  title: FX Rates API
  description: Fetch FX rates of a particular currency
  version: "1.0.0"
host: api.logindy.com
schemes:
  - http
basePath: /v1/fx
produces:
  - application/json
paths:
  /rates:
    get:
      summary: FX Rates
      description: The FX rates endpoint returns buy and sell rates of a particular currency.
      parameters:
        - name: currency_pair
          in: query
          description: Currency pair to fetch its rates.
          required: true
          type: string

The benefits of defining the specification like this is that we can use Swagger UI to test the FX Rate service and produce the document later based on this specification. Now we have the API specification, next step we will create automated acceptance tests.

Automated Acceptance Test

Using Markdown, the test scenarios for a happy path is as follow:

FX Rate Service Specification
=============================

The users must be able to see FX rates for a particular currency therefore; 
they know how much money they need to exchange for that currency.

Tags: FX rates

* User must open the FX Rates page

Successful retrieve
-------------------

Tags: successful

For existing currencies, the FX Rates page will show their rates.

* Current "USD/THB" buy rate is "35.989" and sell rate is "35.890"
* "USD/THB" should show up in the page with a buy rate is "35.989" and a sell rate is "35.890"

To run these test scenarios, we will use Gauge (http://getgauge.io/), the reason that I use this tool because it can translate this business readable Markdown format tests to execute test fixtures written in different programming languages; currently it supports C#, Java and Ruby. Also, it provides a command line tool to execute tests therefore; it is convenient to integrate with CI servers, Jenkins for example.

To create a new Gauge project, we will create a new folder, spring-cd-aat, and in this folder we will run the following command.

gauge --init java

This command will generate required files and folders to develop automated tests. A sample file, hello_word.spec, will be created in the specs folder. Let’s change it to fx_rate_service.spec, put the specification I provided above and run the following command.

gradlew gauge

This will complile the corresponding steps StepImplementation.java file and the Gauge will call the methods of this class as the steps provided in the fx_rate_service.spec.

Since we have not implemented the steps yet, the test will fail and the report will be as the example shown below.

CD_Spring_10.png

Gauge Test Report

Let’s write the test scenario Java codes for running with Gauge. You can find the Gauge Java example at this link https://github.com/getgauge/gauge-example-java. The complete code for the following steps can be found in the GitHub repository path provided at the end of this post.

public class FXRateServiceSpec {
 
 private WebDriver driver;

   public FXRateServiceSpec() {
     this.driver = DriverFactory.getDriver();
   }
 
   @Step("Current <currency pair> buy rate is <buy> and sell rate is <sell>")
   public void currentBuyAndSellRates(String pair, String buy, String sell) {
     System.out.format("%s %s/%s\n", pair, buy, sell);
   }
 
   @Step("<currency pair> should show up in the page with a buy rate is <buy> and a sell rate is <sell>")
   public void shouldShowFXRates(String pair, String buy, String sell) {
     FXRatesPage fxRatesPage = PageFactory.initElements(driver, FXRatesPage.class);
     fxRatesPage.verifyFXRates(0, pair, buy, sell);
   }
 
   @Step("User must open the FX Rates page")
   public void mustOpenFXRatesPage() {
     this.driver.get(FXRatesPage.FXRatesUrl);
   }
}

Setup Jenkins Project for Automated Acceptance Test

Because we put the tests in the spring-cd-aat folder, we have to setup a new project for it. It will test a package built from the spring-cd project. In order to copy the package from the spring-cd to spring-cd-aat project after the build finished, we will install the Copy Artifact plug-in into the Jenkins as the screenshot shown below.

Copy Artifact plug-in

Copy Artifact plug-in

After installing the plug-in, we should create and commit the spring-cd-aat to our local Git repository first so that we can refer it in our new Jenkins project. The settings for the new project are as follows.

The spring-cd-aat project settings

The spring-cd-aat project settings

Lastly, we have to configure the spring-cd project to archive an artifact every item it is successfully built as the screenshot show below.

Archive the artifacts setting

Archive the artifacts setting

This time when the Jenkins finishes building the spring-cd, it will trigger a next job to build the spring-cd-aat as well; both the spring-cd and spring-cd-aat projects should show a blue status. However, so far we have not added a step to run the tests yet, let’s add that step in the spring-cd-aat now.

The first step is to start our Sprint Boot application.

The steps to start Spring Boot application

The steps to start Spring Boot application

The second step is to run the tests with the Gauge.

 

The steps to run test

The steps to run test

Please note that because we are using Selenium to test the application and it requires the screen. If you run the Jenkins as Windows service, it might not be able to interact to Selenium properly therefore; running the Jenkins with ‘java -jar jenkins.war’ is recommended.

The tests will fail when we build the project because we have not implemented the production code yet. Let’s implement our first code to make the tests pass; a simple html page, fx-rates.html, show fixed buy and sell rates for USD/THB. The page will be in the src/main/resources/static path.

Let’s commit the changes to the repository and build the spring-cd again. This time the test will pass and you can see the result in the reports folder in the workspace of the spring-cd-aat project as the following screenshot.

The Gauge test result for the FX Rates Service

The Gauge test result for the FX Rates Service

That’s all for this post, in a next post, we will add more automated acceptance tests and production codes, also add some automated integration tests for our API. You can get the part 2 complete source code by using this command.

git clone https://github.com/kkasemos/spring-cd.git
git checkout -f part2

git clone https://github.com/kkasemos/spring-cd-aat.git
git checkout -f part2

 

Tagged with: , , ,
Posted in Continuous Delivery, Spring Boot, Spring MVC Framework, Test Driven Development

Continuous Delivery (CD) Driven Spring Boot Application Development Part 1 – Setup Spring and Jenkins Project

Continuous Delivery is a software engineering practice being adopted by many companies nowadays because it helps those companies to delivery software fastest and in a more reliable manner.  An automation is one of important parts of CD, it helps to reduce human errors and do repetitive tasks required in a software development; build binary files, run tests or deploy packages to servers for example.

In this post, we will setup a Spring project based on Spring Boot and Jenkins project for our project automation. I choose Spring Boot because it is easy to get started and requires not much configuration and steps to build the project. Also, because we can have Spring Boot application packaged with a web container, Tomcat as the default, it is easy to run integration tests that interact to our application.

Rather using Spring Tool Suite to create a new project, we will build the project using command line tools and only a simple text editor and command-line based tools to do this. We do like this is to get our self familiar with command-line based tools which are, in my view, a heart of automation; typically Jenkins interacts with command-line based tools to automate tasks.

Install Spring CLI

The Spring CLI can be download at this link http://repo.spring.io/release/org/springframework/boot/spring-boot-cli/1.3.0.RELEASE/spring-boot-cli-1.3.0.RELEASE-bin.zip. Please refer to the reference guide here http://docs.spring.io/spring-boot/docs/current/reference/html/cli-using-the-cli.html.

Create Project Directory and Initial File

Using the Spring CLI to create a new project (with the web dependency) as follows:

spring init --build=gradle --dependencies=web spring-cd

The tool will create example files and directories which we will base our project on; here is my build.gradle file’s content:

buildscript {
    ext {
        springBootVersion = '1.3.0.RELEASE'
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}") 
        classpath('io.spring.gradle:dependency-management-plugin:0.5.2.RELEASE')
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot' 
apply plugin: 'io.spring.dependency-management' 

jar {
    baseName = 'demo'
    version = '0.0.1-SNAPSHOT'
}
sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories {
    mavenCentral()
}


dependencies {
    compile('org.springframework.boot:spring-boot-starter-web')
    testCompile('org.springframework.boot:spring-boot-starter-test') 
}


eclipse {
    classpath {
         containers.remove('org.eclipse.jdt.launching.JRE_CONTAINER')
         containers 'org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8'
    }
}

task wrapper(type: Wrapper) {
    gradleVersion = '2.7'
}

Install Gradle

The first command line tool we will install is Gradle. As you might have seen in many Spring tutorials that it is one of recommended tools for building a project. We can download it from the Gradle website and put it in a directory on our local machine. Also, we have to update the PATH environment variable to have a path to the gradle executable file. After we did these steps, we can check if the gradle is setup correctly as follows:

gradle tasks

It should list all current tasks the gradle support. Next steps, we should install a gradle wrapper into the project directory. Run the gradle command as follow:

gradle wrapper

The gradle wrapper is a script file that we will check into a source code repository. the benefit of the wrapper is that when we checkout the source code to other machines we don’t have to go to the Gradle website, download and install it into the machine but just run the wrapper and it will automatically download and install the gradle for us. To check if the wrapper installed correctly, run:

gradlew build

Create Git Repository

After we setup the project directory and installed the gradlew, we will commit our changes to our Git repository. Let’s create the repository by running the ‘git init’ command in the root path of the project and create the following .gitignore file which has content as below.

.gradle
build

The files to be add to the repository will be:

git add build.gradle
git add gradle
git add gradlew.bat
git add src
git add .gitignore
git commit -m "Initial version"

After we committed our changes to the repository, the next steps are to install the Jenkins Gradle plugin and create a new Jenkins project.

Install Jenkins Gradle Plugin

If you don’t have a Jenkins server installed yet, please follow instructions in this link https://wiki.jenkins-ci.org/display/JENKINS/Installing+Jenkins. After installing the Jenkins server, go to the http://localhost:8080, and click the Manage Jenkins > Manage Plugins > Available tab.

Select the Gradle Plugin and click the Install button as the figure shown below.

Jenkins Gradle Plugin

Jenkins Gradle Plugin

Setup Jenkins Project

After installing the Gradle plugin, go back to the Dashboard page and click the New Item menu to open a new project form. Enter a project name and select the Freestyle project and click the OK button.

Freestyle Project

Freestyle Project

In the Freestyle project form, we will put setting values as follows:

Project Settings

Project Settings

I use the Git repository in my local machine that’s why the Repository URL start with the file:// protocol. After putting all the settings, click the Save button and then the Build Now menu to start building our project via the Jenkins. If everything works fine, it should finish the build with the blue status as the below figure.

Build Result

Build Result

That’s all for the Jenkins and project setup. In the next post, we will start to write automated tests for the application. The source code of this post can get from the repository using the commands below.

git clone https://github.com/kkasemos/spring-cd
git checkout -f part1

 

Tagged with: , , ,
Posted in Continuous Delivery, Spring Boot, Spring MVC Framework

Continuous Delivery (CD) Driven .NET Development Part 3 – Test Result Page and Additional Scenarios

In the previous post, we created the first automated acceptance test using SpecFlow and NUnit. In this post, we will create more tests and configure our project to display test result in the Test Report page.

Second Scenario

You might notice that we fix the return value of the Generate method to make it passed the first scenario. This is the TDD technique, we gradually evolve our production code as we add more test cases. Now let’s add the second scenario to the Generate_Company_A_Report.feature as follows:

@HappyPath
Scenario: Have duplicate records
	Given Input is
		| Product Name | Amount |
		| Product A    | 100    |
		| Product B    | 200    |
		| Product C    | 300    |
		| Product B    | 200    |
		| Product A    | 100    |
	When It generates a report
	Then Output is
		| Product Name | Amount | Qty |
		| Product A    | 100    | 2   |
		| Product B    | 200    | 2   |
		| Product C    | 300    | 1   |

When you save the file, the VS will automatically re-generating the corresponding Generate_Company_A_Report.cs file. Re-build the test project and run the new HaveDuplicateRecords test via the Test Explorer, the test will fail as expected because we fixed the return value of the Generate method as mentioned early.

Let’s do the code refactoring to make it passes the second scenario.

// ReportGenerator.cs
public string Generate(string data)
{
    var stringBuilder = new StringBuilder();
    var records = data.Split(new string[] { "\r\n" }, 
        StringSplitOptions.RemoveEmptyEntries)
        .Select(l => l.Split(','));

    var recordGroups = records.GroupBy(
        r => r[0],
        (name, elements) => new
        {
            Name = name,
            Amount = elements.Max(e => e[1]),
            Qty = elements.Count()
        });

    foreach (var rg in recordGroups)
    {
        stringBuilder.AppendLine(string.Format("{0},{1},{2}", 
            rg.Name, rg.Amount, rg.Qty));
    }

    return stringBuilder.ToString();
}

Re-build the ReportGenerator project and run the test again, this time it will pass as expected. Before we proceed to write the third scenario, let’s add the NUnit plugin into the Jenkins therefore; we can publish the NUnit test result in the format can be displayed in the Test Result page.

Install Jenkins NUnit Plugin

To install the plugin, go to the Manage Plugins page and install the NUnit plugin as follows:

Install the NUnit plugin

Install the NUnit plugin

After that we have to configure the Report Generator project to publish the test result as follows:

Add post-build action to publish the test result

Add post-build action to publish the test result

Set a location of the TestResult.xml

Set a location of the TestResult.xml

Save the change and commit our current code changes to the repository and try to build the project via the Jenkins again. When the build finishes, we can see the test result in the Test Result page as an example shown below (the link to the page will be in the Build history page, not in the Dashboard page of the project).

The Test Result page

The Test Result page

We can click the links in the Test Result to drill down more details of the test result, which method fails for example.

Third Scenario

What if the same product name has different prices? After asking the business team, they would like to treat this case as invalid, print an error message into the erro.log file and ignore that product therefore; the scenario will be:

@HappyPath
Scenario: The same product name has different amounts
	Given Input is
		| Product Name | Amount |
		| Product A    | 100    |
		| Product B    | 100    |
		| Product C    | 300    |
		| Product B    | 200    |
		| Product A    | 100    |
	When It generates a report
	Then Output is
		| Product Name | Amount | Qty |
		| Product A    | 100    | 2   |
		| Product C    | 300    | 1   |
	And Error message "Error occurred, please see the error.log file" displayed
	And Error log file is "error.log"
	And Error log file contains 
	"""
	Product B records have different amounts
	"""

Save the change and run the test, it will print out that we have not got the new steps yet. Let’s put them in the step definition file as follows:

// Generate_Company_A_Steps.cs
[Then(@"Error message ""(.*)"" displayed")]
public void ThenErrorMessageDisplayed(string expectedMsg)
{
    ScenarioContext.Current.Pending();
}

[Then(@"Error log file is ""(.*)""")]
public void ThenErrorLogFileIs(string expectedLogFileName)
{
    ScenarioContext.Current.Pending();
}

[Then(@"Error log file contains")]
public void ThenErrorLogFileContains(string expectedContent)
{
    ScenarioContext.Current.Pending();
}

The production code to pass the first Then step will be.

// ReportGenerator.cs
public string Generate(string data)
{
    var stringBuilder = new StringBuilder();
    var records = data.Split(new string[] { "\r\n" }, 
        StringSplitOptions.RemoveEmptyEntries)
        .Select(l => l.Split(','));

    // Group by Product Name and Amount 
    var recordGroups = records.GroupBy(
        r => new { Name = r[0], Amount = r[1] },
        (key, elements) => new
        {
            Name = key.Name,
            Amount = key.Amount,
            Qty = elements.Count()
        });

    // Find the records are duplicate after grouping
    var dupRecords = recordGroups.GroupBy(
        g => g.Name,
        (key, elements) => new
        {
            Name = key,
            Qty = elements.Count()
        })
        .Where(r => r.Qty > 1);

    foreach (var rg in recordGroups)
    {
        if(!dupRecords.Any(d => d.Name.Equals(rg.Name)))
        {
            stringBuilder.AppendLine(string.Format("{0},{1},{2}",
                rg.Name, rg.Amount, rg.Qty));
        }
    }

    return stringBuilder.ToString();
}

When you rebuild the ReportGenerator and its test projects, and run the test again, it will pass the first Then step but fail for the next And steps because we have not implemented them yet. Let’s implement them as follows:

private string message;

// Generate_Company_A_Steps.cs
[Then(@"Error message ""(.*)"" displayed")]
public void ThenErrorMessageDisplayed(string expectedMsg)
{
    Assert.AreEqual(expectedMsg, message.Trim());
}

Since we expect the error message printed on the console, let’s modify the ReportGeneratorDriver also the WhenItGeneratesAReport step to collect the error message from the console as follows:

// ReportGeneratorDriver.cs
public void GenerateReport(string inputFilePath, out string message)
{
    Process process = new Process();
    process.StartInfo.UseShellExecute = false;
    process.StartInfo.RedirectStandardError = true;
    process.StartInfo.FileName = "ReportGenerator.exe";
    process.StartInfo.Arguments = inputFilePath;
    process.Start();
    process.WaitForExit();

    if(process.ExitCode == -1)
    {
        message = process.StandardError.ReadToEnd();
    }
    else
    {
        message = string.Empty;
    }
}

// Generate_Company_A_Steps.cs
[When(@"It generates a report")]
public void WhenItGeneratesAReport()
{
    string tempFileName = Path.GetTempFileName();

    File.WriteAllText(tempFileName, stringBuilder.ToString());
    driver.GenerateReport(tempFileName, out message);
}

And then the production code to make this step passed.

// Program.cs
public static void Main(string[] args)
{
    List dupProducts = new List();
    var reportGenerator = new ReportGenerator();
    var inputData = File.ReadAllText(args[0]);
    var outputData = reportGenerator.Generate(inputData, dupProducts);

    File.WriteAllText("out.txt", outputData);
    if (dupProducts.Count > 0)
    {
        Console.Error.WriteLine("Error occurred, please see the error.log file");
        Environment.Exit(-1);
    }
}

The last two And steps that verify whether the error.log file exists and its content is correct, the code will be:

[Then(@"Error log file is ""(.*)""")]
public void ThenErrorLogFileIs(string expectedLogFileName)
{
    logFileName = expectedLogFileName;
    Assert.IsTrue(File.Exists(expectedLogFileName));
}

[Then(@"Error log file contains")]
public void ThenErrorLogFileContains(string expectedContent)
{
    var content = File.ReadAllText(logFileName);
    Assert.AreEqual(expectedContent, content);
}

The production code that makes the above two steps passed is as follow:

// Program.cs
public static void Main(string[] args)
{
    List dupProducts = new List();
    var reportGenerator = new ReportGenerator();
    var inputData = File.ReadAllText(args[0]);
    var outputData = reportGenerator.Generate(inputData, dupProducts);

    File.WriteAllText("out.txt", outputData);
    if (dupProducts.Count > 0)
    {
        var errorMessages = dupProducts.Select(d => string.Format("{0} records have different amounts", d));
        File.WriteAllText("error.log", string.Join("\n\r", errorMessages));
        Console.Error.WriteLine("Error occurred, please see the error.log file");
        Environment.Exit(-1);
    }
}

Now all the tests pass therefore; let’s commit all the changes to the repository and build the project via the Jenkins server. The build should finish successfully and the Test Result Trend is blue as the screen shown below.

Test Result Trend

Test Result Trend

That all for the Test Result page and the additional scenarios for our application. In the next post, I will show you how to setup the acceptance test project to automatically re-generate a new .cs file every time we build the project. This is helpful when we modify only the feature file to add more scenarios without adding any new steps. With this setting, we don’t have to open the project in the Visual Studio and re-generate the .cs file again, just edit the feature file and build the project via the Jenkins server.

The code for the part 2 and 3 can be found at this link https://github.com/kkasemos/report-generator (please see the tag part-2-3)

Tagged with: ,
Posted in .NET Development, Continuous Delivery, Test Driven Development

Continuous Delivery (CD) Driven .NET Development Part 2 – Automated Acceptance Tests

In the CD, we run automated acceptance tests to verify whether our software meet user requirements. The automated tests will be run every time we make changes in our software; source codes, configuration or resource file changes etc.

We can use MS Test or others test framework to write automated acceptance tests however I prefer the frameworks that provide ways we and users can collaborate to produce acceptance criteria for the requirements. We will use SpecFlow (http://www.specflow.org/), it is an automated test framework that supports Gherkin (https://github.com/cucumber/cucumber/wiki/Gherkin) which describes software’s behavior in business readable language, in my opinion, this helps a lot to clarify what users actually expect from the software in the language that both of us understand.

Solution File Location Correction

In the previous post, we created the ReportGenerator solution without creating a new folder for the solution this cause the solution file is in the same directory as the ReportGenerator project therefore; we have to move it to the parent directory and change the following line in the ReportGenerator.sln file to refer to the correct path of the project file.

Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReportGenerator", "ReportGenerator.csproj", "{2B9F0EC4-AE9A-4234-B7EE-E661CFE3E47A}"

Change as below and save the change.

Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReportGenerator", "ReportGenerator\ReportGenerator.csproj", "{2B9F0EC4-AE9A-4234-B7EE-E661CFE3E47A}"

Also, the path in the Configure page of the Jenkins have to change as follows:

Correct the solution file path

Correct the solution file path

Acceptance Test Project

We will create a new project in the Visual Studio for automated acceptance tests as shown below.

Create the Automated Acceptance Test project

Create the Automated Acceptance Test project

After the project created, we will install NUnit Test Adapter as described in this link http://nunit.org/index.php?p=vsTestAdapter&r=2.6. Lastly, remove the UnitTest1.cs file from the project if it exists.

Install SpecFlow

I recommend to install SpecFlow Extension, please follow the instructions in this link http://www.specflow.org/getting-started/. After installing the extension, we have to upgrade SpecFlow to the latest version as shown below.

Upgrade SpecFlow

Upgrade SpecFlow

Install NuGet and Configure Jenkins

Becaue the test project uses the NUnit and SpecFlow, we have to restore these two packages before we build the test project via the Jenkins. The NuGet executable file can be download at https://www.nuget.org/nuget.exe.

After we downloaded the NuGet, go to the Configure page of the Report Generator project in the Jenkins and add one more build step before the MSBuild step as shown below (each build step can be drag-and-drop to re-order them).

Build step to restore the NuGet packages

Build step to restore the NuGet packages

Restore the NuGet packages

Restore the NuGet packages

Please note that the path to the nuget.exe might need to be changed to match the one installed in your local machine.

User Story

Let’s pretend that we are working at Company XYZ which sell some products to Company A and our business team has to produce sell order report every day; it will be used to forecast demand for our company products. Because it is time consuming task and sometimes they sum all the orders up incorrectly, they would like to eliminate this repetitive and time consuming task. They ask us, IT team, to develop a program to generate a report based on an input file Company A provides every end of day. This is the user story we will work on, the first feature for this user story will be Generate Company A Report.

First Automated Acceptance Test

We will describe our software feature and its acceptance criteria in a feature file (.feature extension), this file will be used by SpecFlow to execute automated tests also if the automated tests do not exist yet, it will generate automated test skeleton for us. To create a feature file,

  1. Open the Add New Item dialog
  2. Select the SpecFlow Feature File
  3. Enter Generate_Company_A_Report.feature in the Name textbox
  4. Click the Add button

Put the content to the feature file as follow.

Feature: Generate Company A Report
	In order to forecast demand for our products from Company A
	As a product manager
	I want to know how many our products Company A sell every day grouped by product name

@HappyPath
Scenario: No duplicate record
	Given Input is
		| Product Name | Amount |
		| Product A    | 100    |
		| Product B    | 200    |
		| Product C    | 300    |
	When It generates a report
	Then Output is
		| Product Name | Amount | Qty |
		| Product A    | 100    | 1   |
		| Product B    | 200    | 1   |
		| Product C    | 300    | 1   |

The beginning of the feature file are title and description; it gives you idea why this feature exists. Next to the description will be one or more scenarios of the feature. The scenario can be tagged by using @tagname; this is used by SpecFlow to group scenarios. Each scenario will have a number of Given/When/Then, these keywords along with text and data we supply will be used to generate an automated test. Please refer to the SpecFlow documentation for more information about its usage. In the example above, it defines the input data and expected output data by using Gherkin table syntax.

When we save the change, normally the SpecFlow extension will automatically generate a .cs file contains the automated test. If the .cs file is not generated, you can right-click on the project and select the Regenerate Feature Files menu. However this automated test is not complete yet we have to provide testing steps which call our code under test.

Try to build the project and open the Test Explorer, we will see the scenario in the feature file shown in the list as shown below.

The scenario in the Test Explorer

The scenario in the Test Explorer

Select the scenario in the Test Explorer and run it. In the Ouput console, we will see the output of the test suggests the code we might put into the testing steps which we are going to create it now. To create the testing steps, we need to create a new SpecFlow Step Definition File, Generate_Company_A_Steps.cs as shown below.

Create the SpecFlow Step Definition

Create the SpecFlow Step Definition

After creating the step definition, open the file, remove the existing methods and copy the source codes in the Output console to the file as follow.

 [Given(@"Input is")]
 public void GivenInputIs(Table table)
 {
     ScenarioContext.Current.Pending();
 }
 [When(@"It generates a report")]
 public void WhenItGeneratesAReport()
 {
     ScenarioContext.Current.Pending();
 }
 [Then(@"Output is")]
 public void ThenOutputIs(Table table)
 {
     ScenarioContext.Current.Pending();
 }

These are the methods represent the steps in the feature file we create early.  The Table type as argument in the GivenInputIs and ThenOutputIs is transformed from the Gherkin table we define in the feature file. Next we will implement each step to call our code.

Before implementing our code, do not forget to add the reference to he ReportGenerator project also the project dependency. We will implement the GivenInput as follows:

 [Given(@"Input is")]
 public void GivenInputIs(Table table)
 {
     stringBuilder = new StringBuilder();

     foreach (var row in table.Rows)
     {
         var name = row["Product Name"];
         var amount = row["Amount"];

         string line = string.Format("{0},{1}", name, amount);
         stringBuilder.AppendLine(line);
     }
 }

The GiveInput is where we setup test data as test case we define. The test data is from the table we define in the feature file which has the Product Name and Amount columns. The code above transforms the two columns to the line which will be used as input when calling the Main method of the Program which will further calls the method of a RecordGenarator object to generate the report. Since we do not have the RecordGenerator class yet, let’s create the class in the RecordGenerator project.

namespace ReportGenerator
{
    public class ReportGenerator
    {
        public string Generate(string data) {}
    }
}

Also, the Program class has to be modified as follows:

public class Program
{
    public static void Main(string[] args)
    {
        var reportGenerator = new ReportGenerator();
        var inputData = File.ReadAllText(args[0]);
        var outputData = reportGenerator.Generate(inputData);
        File.WriteAllText("out.txt", outputData);
    }
}

After that try to build and run the test, the test will print a suggestion message that we have to implement the WhenItGeneratesAReport.

Report Generator Driver for Testing

Referring to the Continuous Delivery book, there is a section explains about the technique which help to reduce coupling between tests and code under test by using a driver which is responsible to automate an application. The method signatures the driver provides will be in form of domain language rather than a step to automate the application. For example, driver.GenerateReport() or driver.ArchiveReport(). Let’s create the new ReportGeneratorDriver class in the project and has the code as follows:

public class ReportGeneratorDriver
{
    public void GenerateReport(string inputFilePath)
    {
        Process process = new Process();
        process.StartInfo.FileName = "ReportGenerator.exe";
        process.StartInfo.Arguments = inputFilePath;
        process.Start();
        process.WaitForExit();
    }
}

Now we have the driver, then let’s implement the WhenItGeneratesAReport  as follows:

 private ReportGeneratorDriver driver = new ReportGeneratorDriver();

 [When(@"It generates a report")]
 public void WhenItGeneratesAReport()
 {
     string tempFileName = Path.GetTempFileName();
     File.WriteAllText(tempFileName, stringBuilder.ToString());
     driver.GenerateReport(tempFileName);
 }

Also modify the Generate method to the ReportGenerator class as follows:

 public string Generate(string data)
 {
     var stringBuilder = new StringBuilder();
     stringBuilder.AppendLine("Product A,100,1");
     stringBuilder.AppendLine("Product B,200,1");
     stringBuilder.AppendLine("Product C,300,1");

     return stringBuilder.ToString();
 }

Finally, implement the ThenOutputIs to check the result as follows:

 [Then(@"Output is")]
 public void ThenOutputIs(Table table)
 {
     var outputString = File.ReadAllText("out.txt");
     var expectedStringBuilder = new StringBuilder();

     foreach (var row in table.Rows)
     {
          var name = row["Product Name"];
          var amount = row["Amount"];
          var qty = row["Qty"];

         string line = string.Format("{0},{1},{2}", name, amount, qty);
         expectedStringBuilder.AppendLine(line);
     }

     Assert.AreEqual(expectedStringBuilder.ToString(), outputString);
 }

When we try to run the test again, the test will pass. Because we should run the test every time we build the Report Generator project in the Jenkins as well, let’s commit the changes to the GIT repository and configure the Jenkins to run the test.

Configure Jenkins to Run Automated Test

We will run the automated test using the NUnit console command which will be in the packages folder where the NuGet installed the re-stored packages. In my local machine, the command line is in D:\programs\jenkins\jobs\Report Generator\workspace\ReportGenerator\packages\NUnit.2.5.7.10213\Tools. To call the command, add one more build step in the Report Generator project as follows:

Build step to run the NUnit for the acceptance tests

Build step to run the NUnit for the acceptance tests

Save the change and try to build the project via the Jenkins again, this time after it finished the build it will run the automated test and the result can be found in the Console Output page of that build something like this.

...
ProcessModel: Default    DomainUsage: Single
Execution Runtime: Default
.Given Input is
  --- table step argument ---
  | Product Name | Amount |
  | Product A    | 100    |
  | Product B    | 200    |
  | Product C    | 300    |
  | Product B    | 200    |
  | Product A    | 100    |
-> done: Generate_Company_A_Steps.GivenInputIs() (0.0s) ...

That’s all for the first automated acceptance test. In the next post, I will show how to setup the project in the Jenkins to be able to display the test result in the Test Result page of the project also add more automated acceptance tests and enable the Jenkins feature that it will build the project every time we commit our code changes to the repository.

Tagged with: , , , ,
Posted in .NET Development, Continuous Delivery, Test Driven Development

Continuous Delivery (CD) Driven .NET Development Part 1 – Continuous Integration Server (Jenkins) Setup

According to Wikipedia (https://en.wikipedia.org/wiki/Continuous_delivery), Continuous Delivery (CD) is a software engineering approach in which teams keep producing valuable software in short cycles and ensure that the software can be reliably released at any time. It is used in software development to automate and improve the process of software delivery.

In this first post of the series, I will show you how to setup a Continuous Integration (CI) server, called “Jenkins”. The CI server is one important component in the CD that help you to automate tasks in each stages of a software development; code building, packing and testing etc.

Jenkins can be download at this link http://mirrors.jenkins-ci.org/windows/latest for the other platforms (Linux etc.) you can find them in the Download page of the website. After downloading the package, extract it to a local machine. For the Windows version, double click on the setup.exe will bring up the Setup Wizards window for setting up the server. After following the instructions in the Wizard to finish the setup, the server will be installed and the default browser in the local machine will be opened with the home page URL (http://localhost:8080/) of Jenkins server as shown below.

The home page of Jenkins server

The home page of Jenkins server

Install Plugins

Since currently I am working at a company that using .NET, I would like to use .NET project as example for my CD driven development. The Jenkins plugins for .NET development need to be installed therefore; it can automate tasks for .NET projects. The list of the plugins can be found here https://wiki.jenkins-ci.org/display/JENKINS/Plugins#Plugins-.NETdevelopment

MSBuild Plugin

To install the plugins, click the Manage Jenkins menu on the left hand side and then click the Manage Plugins menu. In the Manage Plugins page, click the Available tab and put “MSBuild” in the Filter text box to search for the plugin. In the Install column, tick the checkbox and then click the Install without restart button, the Installing page will be shown with the Pending status and it will change to the Success status once the installation is complete as shown below.

The Installing page with the Success status

The Installing page with the Success status

GIT Plugin

Since I will store my project source codes in GIT repository, I have to install the GIT plugin in order for Jenkins server to be ablet to checkout the source code and build them. Go back to the Manage Plugins page again and put the GIT Plugin in the Filter text box to search for the plugin and then tick the checkbox of the GIT Plugin row and click the Install without restart button. After it finished to install, it will shown the message that we need to restart the server as shown below.

The Installing page of the GIT plugin

The Installing page of the GIT plugin

To restart, click the checkbox in front of the Restart Jenkins…. After the server restarted, go back to the Home page and click the create new jobs link to start creating a new job to build our project.

Configure First Jenkins Job

The example project we are going to develop is called “Report Generator”, it is a simple program to generate reports based on input CSV files provided by 3rd parties. The report files will be CSV format as well but contain additional columns or only some columns from the input files. Put the project name and description as shown below.

The Configure page - Project name and description

The Configure page – Project name and description

However, in the Windows, the Jenkins server run as service which belongs to a different user account therefore; it might fail to call the git command during it checks out the source codes. To find out the user account the server belong to and if the git is in the PATH environment variable, we will put the commands (echo %PATH% and whoami) in the Configure page as follows:

The Build Windows command

The Build Windows command

The Windows command to print the username and PATH

The Windows command to print the username and PATH

Save the change by click the Save button at the bottom, then start run the job by click the Build Now menu on the left hand side. The result of the job will be displayed on the Build History which you can click the link in front of each row to see more details. Click the link to open the Build page and then click the Console Output menu on the left hand side to see the log. You will see the result for the Windows commands we put early which tell us whether the git is in the PATH and the user account the server belongs to as the example shown below.

The Console Output for the Windows commands

The Console Output for the Windows commands

In my case, the git is not in the PATH and the user account is the authority\system which is not the user account I use to login therefore; I have to change the user account the server is using; going to the Service panel and change the Log On as described in these links  http://stackoverflow.com/questions/15257652/jenkins-configuration-of-git-plugin and http://antagonisticpleiotropy.blogspot.com/2012/08/running-jenkins-in-windows-with-regular.html, after changing you have to restart the Jenkins service.

Once we solve the user account problem, the next step is to put the local path to the repository of our project in the Configure page. I will put it in here D:\projects\report-generator and save the change (You might have to run the git init command in this folder before referring to it in the Configure page).

The GIT repository path

The GIT repository path

Note: the types of path to a repository the git supports, http://stackoverflow.com/questions/978052/githow-can-i-make-my-local-repository-available-for-git-pull.

Try to run the Build Now again, this time it will show the following error in the Console Output because we have not got any code to build yet.

ERROR: Couldn’t find any revision to build. Verify the repository and branch configuration for this job. Finished: FAILURE

Commit the new readme.txt file to the repository which might contains some things like this.

Report Generator
A program to generate reports for the business team based on input CSV files from 3rd parties.

Try to run the Build Now again, this time it will not fail and the Console Output will show the changes it checks out from the repository as the example shown below.

Started by user anonymous
Building in workspace D:\programs\Jenkins\jobs\Report Generator\workspace > git.exe rev-parse –is-inside-work-tree # timeout=10
Fetching changes from the remote Git repository > git.exe config remote.origin.url file:///D:\projects\report-generator # timeout=10
Fetching upstream changes from file:///D:\projects\report-generator > git.exe –version # timeout=10 > git.exe -c core.askpass=true fetch –tags –progress file:///D:\projects\report-generator +refs/heads/*:refs/remotes/origin/* > git.exe rev-parse “refs/remotes/origin/master^{commit}” # timeout=10 > git.exe rev-parse “refs/remotes/origin/origin/master^{commit}” # timeout=10
Checking out Revision 26fee0bc71f98d3eb878b5c84433e94bfb2e953c (refs/remotes/origin/master) > git.exe config core.sparsecheckout # timeout=10 > git.exe checkout -f 26fee0bc71f98d3eb878b5c84433e94bfb2e953c First time build. Skipping changelog.

First .NET Project

So far we have not got any .NET project yet just the repository to store our source code therefore; let’s create a new .NET project now. Please note that I am using Visual Studio 2015 Community Edition the steps to create the project might be different from the other versions.

Open the VS and create the new project which its source codes will be in the path we given in the previous section: D:\projects\report-generator as shown below. Please uncheck the Create directory for solution because we need not to create a sub folder for the solution in the repository.

The New Project dialog for the Report Generator

The New Project dialog for the Report Generator

After the project created, create a .gitignore file in the project directory this can be done by right clicking on the Solution in the Solution Explorer and select the Add Solution toSource Control… , this will automatically add the .gitignore file specific for the .NET project. Now we have all the initial files required for the project therefore; let’s commit the changes to the repository by clicking the Team Explorer tab, enter commit messages and click the Commit button as shown below.

The Team Explorer to commit the changes

The Team Explorer to commit the changes

Configure MSBuild

We already installed the MSBuild plugin however have not configured it for our project yet. To configure the path to the MSBuild in our local machine, we have to go to the Manage Jenkins page and click the Configure System. In the MSBuild section, put the settings as shown below. Please note that the full path the MSBuild in your machine might different from the example; you can find the path as described in this link https://msdn.microsoft.com/en-us/library/bb397428.aspx

The MSBuild settings

The MSBuild settings

After putting the settings and save the change, go back to the Configure page of the project and delete the Build for the Windows command we put early, add a new one for building our project and save the change as shown below.

Configure the MSBuild of the project

Configure the MSBuild of the project

Try to run the Build Now again, this time it will build successfully and the result for the build can be found in the Jenkins workspace directory, in my local machine it is in D:\programs\Jenkins\jobs\Report Generator\workspace. We can obtain the path from the Console Output of the Build, something like this.

Building in workspace D:\programs\Jenkins\jobs\Report Generator\workspace

That’s all about how to setup the CI server for .NET development. In the next post, I will show you have to setup our project for writing automated acceptance tests. You can find the source codes in the GitHub at this url: https://github.com/kkasemos/report-generator (please see the tag part-1)

Tagged with: , ,
Posted in .NET Development, Continuous Delivery

Spring MVC Based Application Test Driven Development Part 7 – Product Search Form And External RESTful Service Test

In this post, I will show you how we can test an external RESTful service on which our application depends without having to interact with that service directly. However in order to achieve this we have know all returned data formats and URLs of the service we have to deal with.

The Spring Test Framework provides the MockRestServiceServer class to simulate a RESTful service which we can use to write automated unit tests. This class has several methods to set expected request parameters and response data and content type etc.

First Failing Test For External RESTful Service

Assuming that our product search feature will keep tracks of all search requests from visitor by using external RESTful service provided by third party provider. The returned data formats and its corresponding URL are as follows:

URL: http://tokeeptracks.com/searchkeyword

HTTP method: POST

JSON request content format:

{ “api_key”: “mykey”, “api_secret”:”mysecret”, “keyword”:”Nice Shoes”}

JSON response content format:

Success

{ “stauts_code”:0,”status_text”:”success”}

Fail – blank keyword

{ “stauts_code”:1,”status_text”:”blank keyword”}

In a real RESTful service, JSON request and response contents might contain a lot of fields than an example given above however regardless how much a number of fields JSON has, the approach we are going to implement should work for them as well.

Setting Up MockRestServiceServer

Since an external RESTful service is considered as not a component of our core application, we should separate it to a new class rather than calling it directly in our application. We will create a failing test for a new class named, ToKeepTracksServiceTest, as follows:

SpringMVCTDD Part7 ToKeepTracksServiceTest

The first thing we will put into this test class is the mockServer field and the resourceBundle as shown below.

package com.mycompany.ppms.service.external;

import static org.junit.Assert.*;

import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.client.MockRestServiceServer;
import org.springframework.web.client.RestTemplate;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:com/mycompany/ppms/service/external/test-tokeeptracks-service-context.xml")
public class ToKeepTracksServiceTest {

  @Autowired
  private ToKeepTracksService service;

  @Autowired
  private RestTemplate restTemplate;

  private MockRestServiceServer mockServer;

  private ResourceBundle resourceBundle;

  @Before
  public void setUp() throws Exception {
    this.mockServer = MockRestServiceServer.createServer(restTemplate);
    this.resourceBundle = PropertyResourceBundle.getBundle("ToKeepTracksServiceJsons");
  }

  @After
  public void tearDown() throws Exception {
  }

  @Test
  public void testSearchKeyword() {
    String keyword = "Nice Shoes";
    String responseBody = this.resourceBundle.getString("testSearchKeyword");

    this.mockServer.expect(requestTo(toKeepTracksService.getUrlOfSearchKeyword()))
      .andExpect(method(HttpMethod.POST))
      .andExpect(jsonPath("$.api_key").value("mykey"))
      .andExpect(jsonPath("$.api_secret").value("mysecret"))
      .andExpect(jsonPath("$.keyword").value(keyword))
      .andRespond(withSuccess(responseBody , MediaType.APPLICATION_JSON));

    String result = toKeepTracksService.searchKeyword(keyword);

    assertEquals(responseBody, result);
  }
}

The resourceBundle use to load a JSON string from a file, the reason that I load a JSON string from file rather than put it directly to the code because a JSON string contains a lot of ” characters, when we put the string to our code we have to escape them with \”. This make a JSON string hard to read and copied to re-use with other applications.

In the test above, we use the MockRestServiceServer instance verify whether the RestTemplate instance sends a correct request to the server and simulate a corresponding response back to the RestTemplate.

The expected request must be sent to the same URL as the return value from the getUrlOfSearchKeyword() method, a HTTP method must be POST, and a  JSON string must contain the api_key, api_secret and keyword fields with the “mykey”, “mysecret” and keyword value respectively.

Now we have the test, next we need to create the three missing things, ToKeepTracksService class, and the two files, ToKeepTracksServiceJsons.properties and test-tokeeptracks-service-context.xml.

Create the ToKeepTracksService.java file in the src/main/java/com/mycompany/ppms/service/external and put the following lines into it.

package com.mycompany.ppms.service.external;
import org.springframework.stereotype.Service;

public class ToKeepTracksService {

  public String getUrlOfSearchKeyword() {
    return null;
  }

  public String searchKeyword(String keyword) {
    return null;
  }
}

Create the ToKeepTracksServiceJsons.properties file as follows:

SpringMVCTDD Part7 ToKeepTracksJsons.properties

Put the following content into the file.

testSearchKeyword={"stauts_code":0,"status_text":"success"}

Lastly create the test-tokeeptracks-service-context.xml file as follows.

SpringMVCTDD Part7 test-tokeeptracks-service-context.xml

Put the below content into the file:

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

  <bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
</bean>
  <bean id="toKeepTracksService" class="com.mycompany.ppms.service.external.ToKeepTracksService">
  </bean>

</beans>

The bean configuration, the restTemplate and service, will be used in an instance injection for corresponding fields in the ToKeepTracksServiceTest class. Save all the changes we have made to these files and try running the testSearchKeyword() as JUnit test, the test will fail as expected because we have no implementation to make the test passed yet.

Production Code for The ToKeepTracksService

Let’s start with the getUrlOfSearchKeyword() method first, the simplest one. To make it configurable, we will add the setter method as follows:

@Service("toKeepTracksService")
public class ToKeepTracksService {

  private String urlOfSearchKeyword;
  RestOperations restTemplate;

  public void setUrlOfSearchKeyword(String urlOfSearchKeyword) {
    this.urlOfSearchKeyword = urlOfSearchKeyword;
  }
  public String getUrlOfSearchKeyword() {
    return urlOfSearchKeyword;
  }

  public RestOperations getRestTemplate() {
    return restTemplate;
  }

  public void setRestTemplate(RestOperations restTemplate) {
    this.restTemplate = restTemplate;
  }

...
}

How we can configure this URL, via the test-tokeeptracks-service-context.xml as shown below.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"...>

<bean id="toKeepTracksService" class="com.mycompany.ppms.service.external.ToKeepTracksService">
  <property name="urlOfSearchKeyword" value="http://www.tokeeptracks.com/searchkeyword"></property>
  <property name="restTemplate" ref="restTemplate"></property>
</bean>
...
</beans>

Now we have the URL to the service. Let’s add the code makes the test passed into the ToKeepTracksService as follows:

public String searchKeyword(String keyword) {
  String jsonRequest = String.format("{\"api_key\":\"mykey\",\"api_secret\":\"mysecret\",\"keyword\":\"%s\"}",keyword);
  return restTemplate.postForObject(urlOfSearchKeyword, jsonRequest, String.class);
}

This code uses the RestTemplate instance, restTemplate, to send a REST request to the URL defined in the urlOfSearchKeyword field. The request body is a JSON string which its value is as in the jsonRequest variable and we expect the response is a String.class type. Save the change we have made to the ToKeepTracksService and try to re-run the test again, the test will pass.

Second Failing Test for Failure Scenario

Next we will add a new failing test for the scenario that we send a blank keyword to the service as follows:

@Test
public void testSearchKeywordBlankKeyword() {
  String keyword = "";
  String responseBody = this.resourceBundle.getString("testSearchKeywordBlankKeyword");

  this.mockServer.expect(requestTo(service.getUrlOfSearchKeyword()))
    .andExpect(method(HttpMethod.POST))
    .andExpect(jsonPath("$.api_key").value("mykey"))
    .andExpect(jsonPath("$.api_secret").value("mysecret"))
    .andExpect(jsonPath("$.keyword").value(keyword))
    .andRespond(withSuccess(responseBody , MediaType.APPLICATION_JSON));

    String result = service.searchKeyword(keyword);

    assertEquals(responseBody, result);
}

As you can see in the code above, we refer to the testSearchKeywordBlankKeyword property we have to add it to the ToKeepTracksServiceJson.properties as shown below.

testSearchKeywordBlankKeyword={"stauts_code":1,"status_text":"blank keyword"}

Run the test, it will pass because our implementation supports this test case.

Third Failing Test for HTTP Status Internal Server Error

What if we would like to simulate an HTTP error status, Internal Server Error (500) or No Content Found (404) for example. We can simulate this kind of scenario as well by using the MockRestServiceServer. The test for the Internal Server Error is as shown below.

...
import static org.springframework.test.web.client.response.MockRestResponseCreators.withServerError;
...

@Test
public void testSearchKeywordServerError() {
  String keyword = "";

  this.mockServer.expect(requestTo(service.getUrlOfSearchKeyword()))
   .andExpect(method(HttpMethod.POST))
   .andExpect(jsonPath("$.api_key").value("mykey"))
   .andExpect(jsonPath("$.api_secret").value("mysecret"))
   .andExpect(jsonPath("$.keyword").value(keyword))
   .andRespond(withServerError());

  try {
    String result = service.searchKeyword(keyword);
  } catch (ToKeepTracksServiceException e) {
    assertEquals("server error", e.getMessage());
  }
}

As you can see in the code above, we use the withServerError() to simulate the error and our test expect the ToKeepTracksServiceException thrown with the “server error” message in this scenario.

Let’s create the ToKeepTracksServiceException class in the src/main/java/com/mycompany/ppms/service/external folder as follows:

package com.mycompany.ppms.service.external;

public class ToKeepTracksServiceException extends Exception {
  public ToKeepTracksServiceException(String message) {
    super(message);
  }
}

Also, we have to change the signature of the ToKeepTracksService.searchKeyword() method to have the throws statement.

public String searchKeyword(String keyword) throws ToKeepTracksServiceException {
...
}

Try to run the test, it will fail because no implementation to throw the exception yet; the test fails because the HttpServerErrorException thrown. The implementation for this scenario will be:

public String searchKeyword(String keyword) throws ToKeepTracksServiceException {

  String jsonRequest = String.format("{\"api_key\":\"mykey\",\"api_secret\":\"mysecret\",\"keyword\":\"%s\"}",keyword);
  String jsonResponse = null;

  try {
    jsonResponse = restTemplate.postForObject(urlOfSearchKeyword, jsonRequest, String.class);  
  } catch(HttpServerErrorException e) {
    throw new ToKeepTracksServiceException("server error");
  }
  return jsonResponse;
}

When we re-run the test this time, it will pass as expected. Now it is time to integrate the ToKeepTracksService to the product search form, open the ProductSearch.java and put the following code into it.

@Controller
public class ProductSearch {

  @Autowired
  ProductService productService;

  @Autowired
  ToKeepTracksService toKeepTracksService;

  @ResponseBody
  public Map<String, Object> productSearch(@RequestParam("q") String q) throws IOException {
  ...
    try {
      toKeepTracksService.searchKeyword(keyword);
    } catch (ToKeepTracksServiceException e) {
      logger.error("searchKeyword error {}", e.getMessage());
    }
    return jsonMap;
  }
  ...
}

Also, we have to add the bean definition for the toKeepTracksService field into the root-context.xml as follows:

<?xml version="1.0" encoding="UTF-8"?>
<beans ...>
...
  <bean id="toKeepTracksService" class="com.mycompany.ppms.service.external.ToKeepTracksService">
    <bean id="restTemplate" class="org.springframework.web.client.RestTemplate">
    <property name="urlOfSearchKeyword" value="http://www.tokeeptracks.com/searchkeyword"></property>
    <property name="restTemplate" ref="restTemplate"></property>
  </bean>
  </bean>
</beans>

Save the change and start the Tomcat server to test with the real service. This time when we send a search request to our RESTFul service, it will send a request to store a search keyword to the real service. That all for how to test an external RESTFul service used by Spring MVC based application.

However, the change we have made will cause the ProductSearchTest fails because the ProductSearch class now depends on the ToKeepTracksService which will not available when we run the tests.  In the next post, I will show you how to use Mockito (the popular mock framework) to isolate tests among the classes in our application. The source code for the part 7 can be found at this link https://github.com/kkasemos/spring-mvc-tdd/tree/part7.

 

Posted in Spring MVC Framework, Spring Test Framework, Test Driven Development

Spring MVC Based Application Test Driven Development Part 6 – Product Search Form Integration Test With Tomcat 7 Maven Plugin

In the previous post, Spring MVC Based Application Test Driven Development Part 5 – Product Search Form, we have done an integration test for the product search form however the test requires that a web server to be started before running the test. The web server we use in the previous post is the default one provided by the STS; VMware vFabric tc Server Developer.

So far I have not known how to automatically start, deploy and shutdown this server after running the integration test therefore; I use the Tomcat 7 Maven plugin instead. With this plugin, we can configure the Manven to start the Tomcat 7 embedded version, deploy our application, run the test and shutdown the server after the test finishes.

Setting up Tomcat 7 Maven Plugin

How to set up the plugin, I got the steps from this site, http://cupofjava.de/blog/2013/02/05/integration-tests-with-maven-and-tomcat/

Open the pom.xml file of our project and add configuration for the Tomcat 7 plugin as follows:

<!-- For front-end testing -->
<plugin>
  <groupId>org.apache.tomcat.maven</groupId>
  <artifactId>tomcat7-maven-plugin</artifactId>
  <version>2.1</version>
  <executions>
    <execution>
      <id>start-tomcat</id>
      <phase>pre-integration-test</phase>
      <goals>
        <goal>run</goal>
      </goals>
      <configuration>
        <fork>true</fork>
      </configuration>
    </execution>
    <execution>
      <id>stop-tomcat</id>
      <phase>post-integration-test</phase>
      <goals>
        <goal>shutdown</goal>
      </goals>
    </execution>
  </executions>
  <configuration>
    <uriEncoding>UTF-8</uriEncoding>
  </configuration>
</plugin>

The configuration instructs the Maven to run the plugin in the pre-integration-test and post-integration-test phase respectively. For the first one, we want to start up an Tomcat 7 instance and the latter we want to shutdown the instance.

Also, configure the Tomcat 7 to encode an URI by using UTF-8; this is required if you use parts of URI as request parameter. This will ensure non-English characters get parsed from the URI encoded correctly.

Beside the configuration for the Tomcat 7 plugin, we have to configure the Surefire and Failsafe to run only unit tests and integration tests respectively. The configuration is modified from the example in the http://cupofjava.de/blog/2013/02/05/integration-tests-with-maven-and-tomcat/ as well (change to the higher version) as follows:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>2.16</version>
  <configuration>
    <excludes>
      <exclude>**/*IntegrationTest*</exclude>
    </excludes>
  </configuration>
</plugin>
<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-failsafe-plugin</artifactId>
  <version>2.16</version>
  <configuration>
    <includes>
      <include>**/*IntegrationTest*</include>
    </includes>
  </configuration>
  <executions>
    <execution>
      <goals>
        <goal>integration-test</goal>
        <goal>verify</goal>
      </goals>
    </execution>
  </executions>
</plugin>

As you can see in the configuration above, the Surefire will exclude classes which their name contains “IntegrationTest” when it runs the test. On the other hand, the Failsafe will run only those excluded classes.

We use the Failsafe for the integration test because it will not stop running when encountering test failures whereas the Surefire does. The integration test will be executed when we run the Maven with the verify or integration-test goal.

Next, we have to add the Maven Run Configuration in the STS as the steps in the figure shown below.

SpringMVCTDD Maven Run Configuration

Note that if you are going to run the Tomcat 7 plugin to test a web application that read test data from files and want to specify particular file encoding to match with those files, you might need to setup the STS to use an external Maven runtime instead (as the 5th box in the figure) rather than the embedded version shipped with the STS. I encountered a problem that the embedded version ignored the -Dfile.encoding parameter and causes contents read from files encoded incorrectly thus; tests kept failing.

After we finish setup the Maven Run Configuration for the Tomcat 7 plugin, try to run this maven build and then you will see in the Console panel that the Tomcat 7 start up and shutdown during the integration test as the example shown below. The Firefox will be run automatically as well to test our product search form.

[INFO] create webapp with contextPath: /ppms
เธ?.เธข. 21, 2556 3:39:47 เธ?เน?เธญเธ?เน€เธ—เธตเน?เธขเธ? org.apache.coyote.AbstractProtocol init
INFO: Initializing ProtocolHandler ["http-bio-8080"]
เธ?.เธข. 21, 2556 3:39:47 เธ?เน?เธญเธ?เน€เธ—เธตเน?เธขเธ? org.apache.catalina.core.StandardService startInternal
INFO: Starting service Tomcat
เธ?.เธข. 21, 2556 3:39:47 เธ?เน?เธญเธ?เน€เธ—เธตเน?เธขเธ? org.apache.catalina.core.StandardEngine startInternal
INFO: Starting Servlet Engine: Apache Tomcat/7.0.37

That’s all for the integration test with the Tomcat 7 Maven plugin, and you can find the source code for this part at this link: https://github.com/kkasemos/spring-mvc-tdd/tree/part6

In the next post, I will show you how the Spring Test Framework can help us to test our code that depends on an external RETSful service without having to connect to that external service at all by using the RestTemplate and MockRestServiceServer classes.

Tagged with: , , ,
Posted in Application, Spring MVC Framework, Spring Test Framework, Test Driven Development
Follow

Get every new post delivered to your Inbox.