Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
anthony_bateman3
Participant

Introduction


In this blog post, I will explain:

  • What Behavior Driven Development is

  • Why it is useful

  • What Spock is and how it implements BDD

  • How you can test any transform or chain of transforms in CPI from the comfort of your IDE.


What is Behavior Driven Development?


First off, 'Behavior Driven Development is such a mouthful, I shall henceforth refer to it as BDD.

BDD is an evolution and refinement of Test Driven Development (TDD). In TDD, software unit tests are written that test a specific aspect of a program. The typical process of TDD is:

  • Write a simple test for one aspect of the program

  • watch test fail

  • write code to make the test pass

  • refactor


TDD focuses on code functionality at a fairly technical level; as such, it requires a certain amount of technical familiarity to understand what the tests are actually doing.

BDD on the other hand is more concerned with describing features of the software in a way that can be understood by people with different skill sets, such as business experts, testers, developers etc. The way that this is done is by standardizing on a natural language description of how the software is behaving (by natural language I mean any language that has evolved naturally in humans). The advantages of this are:

  • More effective cross-domain conversation about the nature of the features of the software

  • Easier to understand the structure of the tests

  • Easier to maintain the tests

  • Self-documenting tests


There have been several excellent blog posts that have addressed the issue of testing transformations written in Groovy code, but to my knowledge none have addressed the issue of how to test non-Groovy transforms or a chain of steps containing multiple different types of transforms (XSLT, message mappings, java mappings, Groovy mappings etc.).

Enter Spock - the Logical Choice


My work with CPI has meant that I have had to invest time and effort in working with Groovy, somehow that led me to investigating test tools; Spock is a BDD framework written in Groovy, and has some very nice features that take advantage of the Groovy language that I will subsequently describe.

Firstly, each test method can be written as natural language text in quotes, for example:
def "FileHandler reads contents of file"() {

}

In Spock terminology, this is known as a feature method, which describes a property of the system under specification. In this way, you can specify exactly the purpose of the test in a way that is broadly understandable.

Secondly, Spock organizes a feature method into blocks. Each block has a specific purpose. The typical phases of a  feature method are:

  • setup

  • stimulus to the system under specification

  • response expected from the system

  • cleanup


Blocks assist in organising these phases

So without explaining any more, see if this makes intuitive sense to you:
    def "FileHandler reads contents of file"() {
given: "a FileHandler instance"
FileHandler fileHandler = new FileHandler(pathAndFileName)
def contents

when: "I read the contents of the file"
contents = fileHandler.readFileContents()

then: "I expect the contents to be non-null"
contents != null
}
}

In the above example, given, when and then are examples of blocks: 'given' is equivalent to a setup method, 'when' contains the stimulus, and 'then' the expected response. These are not the only blocks, but are most commonly used.

Thirdly, Spock makes parametric testing a breeze and so much easier that with JUnit for example. If one wishes to repeat the same test using different values of input data and expected response, one can use the 'where' block with a number of different data input methods. One of the easiest is to use data tables, which contain parameter names in the header and a row for each test case and expected result:
    def "FileHandler reads contents of file"() {
given: "a FileHandler instance"
FileHandler fileHandler = new FileHandler(pathAndFileName)
def contents

when: "I read the contents of the file"
contents = fileHandler.readFileContents()

then: "I expect the contents to be non-null"
contents != null

where: "test files are"
pathAndFileName || _
'tests/Test1.xml' || _
'tests/Test2.xml' || _

}
}

(The '||' separates input fields from output. In this case, because we have no expected output because in this simple test we are testing for non-null output, Spock requires the '_' to be placed in the output field).

In this case, Spock will iterate through each row of the table, replacing the variables in the column headers with the values specified in the test code.

When such a Feature method is run, the following output is produced in my IDE:


Results of running feature method of Spock


Hopefully, this has given you some idea of some of the power and elegance of Spock.

Refactoring CPI for Testing


Before we can get to testing our Transforms with Spock, we need to do some refactoring. The motivation for this is:

  • We need to be able to test our transform functionality as-is, with no copying or subsequent adjustments after refactoring.

  • We need to provide an endpoint by which we can access the transform functionality.


Let's consider a very simple integration flow, in which we have an XSL Transform followed by a Message Mapping:


Example iFlow with Mapping Actions


Notice that there are three steps to the mapping: a Content Modifier that sets parameters used in the XSL mapping, an XSL Transform, and a Message Mapping. Other steps are superfluous from the point of view of message transformation, and all three steps need to be available to test as a transformational unit.

These are the refactoring steps:

Step 1: Identify the transform related actions


Those are the three steps mentioned above in this example.

Step 2: Isolate the transformation actions into a Local Integration Process


Here, we take those 3 steps and encapsulate them in their own Local Integration process:


Transform Local Integration Process


 

Step 3: Expose an http adapter endpoint to test the Transform Process


Connect the http adapter to its own process, and call the Transform Local Integration Process from it:


BDD Test Process


 

Overall, the refactored integration flow now looks like this:


Process Overview


Having refactored the process:

  • We don't make any changes to the main process steps not involved with transformation activities.

  • The main process is never called in testing, guaranteeing no spurious accidental outputs.

  • The same transformational process is called by both the test process and the main process, eliminating duplicate code/actions.


Setting up your IDE for use with Spock


I won't repeat what has already been published in some excellent articles, so I refer you to these:

Writing A Spock Test


Here is what I have done; firstly, I have written a couple of very simple helper classes to make the code a little cleaner.

The FileHandler.groovy class uses the utility of Groovy to extract the contents of a file:
class FileHandler {
File file

FileHandler(String filePathAndName){
file = new File(filePathAndName)
}

String readFileContents(){
// Read contents of file
return file.text
}
}

The HTTPConnectionHandler is a wrapper around the Java URL class and uses the Apache Commons IOUtils class to make things succinct:
import org.apache.commons.io.IOUtils

class HTTPConnectionHandler {

HttpURLConnection con

HTTPConnectionHandler(String urlAddress, String user, String password){

def userpass = user + ':' + password
def basicAuth = 'Basic ' + new String(Base64.getEncoder().encode(userpass.getBytes()))


URL url = new URL(urlAddress)

con = url.openConnection()
con.setRequestMethod('POST')
con.setRequestProperty ('Authorization', basicAuth)
con.setRequestProperty('Content-Type', 'application/xml; utf-8')
con.setRequestProperty('Accept', 'application/xml')
con.setDoOutput(true)

}

void setRequestBody(String body){
try(OutputStream os = con.getOutputStream()) {
IOUtils.write(body, os, 'UTF-8')
}
}

String getResponse(){
try(InputStream inputStream = con.getInputStream()) {
def response = IOUtils.toString(inputStream)

return response
}

}
}

Having defined these, they are used in our Spock Test Class.

Here is how I did it in IntelliJ IDEA:

1. Define a new Spock Specification



 


First, I set up the 'given' block:
class CPI_Transform_Specification extends Specification {
def "Testing CPI Transform"() {

given: "I set up the FileHandler and HTTPConnectionHandler"
FileHandler fileHandler = new FileHandler(pathAndFileName)
def fileContents = fileHandler.readFileContents()
def urlAddress = 'https://xxxxxxx.xx-xxxxxx-rt.cfapps.eu20.hana.ondemand.com/http/http_test_test'
def user = 'xx-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
def password = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxxxxxxxxxxxxx-xx-xxxxxxxxxxxxxxxxxxxxxxxxxxxx'

HTTPConnectionHandler ch = new HTTPConnectionHandler(urlAddress, user, password)
ch.setRequestBody(fileHandler.readFileContents())

}
}

where urlAddress is the url of the endpoint of the HTTP adapter used in your integration flow and user and password are the clientid and client secret taken from a credential of the Process Integration Runtime:


This code creates a FileHandler, reads the contents of the file passed to it, then sets that as the body of the HTTP POST call that will be made into the iFlow. Note that we haven't specified yet what the parameter pathAndFileName is set to. This is important.

Next, the 'when' stimulus block is set up:
        when: "I call the connection handler to fetch the response from CPI"
def response = ch.getResponse()
def responseXml = new XmlSlurper().parseText(response)

All I do here is get the HTTP Response back from the HTTP call to the Integration Flow, then create a new Groovy XmlSlurper instance to handle querying the content of the response.

 

Next, the 'then' block. These are the actual tests you want to do on the response to make sure the transform did what you expected:
        then: "I expect the following fields to match the expected results"
responseXml.IDOC.E1EDK01.CURCY == curcy
responseXml.IDOC.E1EDK01.HWAER == hwaer
responseXml.IDOC.E1EDK01.NTGEW == ntgew

The XmlSlurper class uses a syntax similar to XPath when specifying a path to an XML element; this is well documented online.

Note that each line is an assertion written in a concise way that does not require the use of the 'assert' keyword as in JUnit tests, making the tests easier to read. Also note that there are 3 variables on the right side of the '==' operators which also haven't been defined yet.

Finally the 'where' block is added:
        where: "test files and expected results are"
pathAndFileName || curcy | hwaer | ntgew
'tests/INVOIC_CU Test1.xml' || 'SEK' | 'SEK' | 153.120
'tests/INVOIC_CU Test2.xml' || 'GBP' | 'GBP' | 221.937

This is one of the really nice features of Spock testing. This is an example of a Datatable, and is how parameterized testing is performed. Note that:

  1. The variables that we commented on earlier are specified here as column headers.

  2. The values that these variables will adopt are written below the variable

  3. Variables to the left of the '| |' symbol are input variables.

  4. Variables to the right of the '| |' symbol are output, or expected variables.


Each row is a test; the table is iterated through a line at a time, and the 'given', 'when' and 'then' blocks are executed for each row of the table.

So this is effectively what is happening with our tests:



2. Run the Tests


When I run this test, if all goes well, this is what I see:


Nice; each test has its own line along with the parameters used.

What if one of the tests fails? How much useful information do I get? Let's force a failure by changing the second line of the Datatable:


Now, when this is run we get the following error message:


Notice that we are told what test failed, and we can see exactly where the falsehood that caused the test to fail occurred. This is very useful in cases of complex logic.

 

2. Extend the Tests


So how easy is it to write a new test? Really easy! You can add new test cases or add new tests to existing test cases just by extending the datatable.

2.1 Add a New Test


To do this, just add a new line to the Datatable:


Now when you re-run the tests, the new line is automatically evaluated:



2.1 Add a New Parameter


To do this, add a new parameter to the database (in this case gewei) and include it in the 'then' block as another check:



Now, when this is run, the new parameter is picked up and the check performed:



Summary


Hopefully in this blog post I have communicated

  • What BDD tests are.

  • Why they are useful.

  • How to refactor your CPI Integration Flow to make the transforms testable.

  • How to install Spock on your IDE.

  • How to write a Spock test.

  • How easy it is to extend your tests by adding new cases or new parameters.


Next Steps


One thing I haven't touched on is the issue with using a Mock adapter to replace calls to external systems. I have been thinking that a way to do this would be to extend the standard adapters to make them mockable. As far as I can tell, CPI Adapters are based on OSGi,  which, to my understanding is open source and hence the source code should be available, at least in theory. Unfortunately, I have posted a question regarding this in the SAP Community, but have had no clarification, so I'm a bit stuck on this.

If anybody can enlighten me on this issue, please comment on this blog post.

Thank you for taking the time to read this! I hope it was useful and easy to understand.

Acknowledgements


I'd like to thank my colleague Jukka Saralehto for mulling this idea over with me and providing the final missing piece to the puzzle; to my ex-colleague Antti Kurkinen for kindly enduring my endless inane questions, and to engswee.yeoh for his excellent and inspiring articles! Finally, to all of you for being part of the community 🙂
4 Comments
Labels in this area