How do you test in an agile environment? Part II

Part I of this article addressed how to capture business requirements both with examples to enable everyone in the team to have a shared understanding of requirements, and details to help with designing code and tests.

This part of the article will concentrate on how to test (in particular to automate) these requirements and deliver a solution without any significant delay.

Testing Pyramid

Figure 1 Testing Pyramid

We will apply the testing pyramid approach; automate as many cases as possible at the lowest achievable level.

The business requirement: User has to enter a valid amount into the textbox to execute a spot trade on a tile.

There are 4 levels of functional testing we need to consider:

  1. Unit tests
  2. Component tests
  3. Integration tests
  4. UAT & System Tests

The system tests are done last and involve using real trade systems on the customer site. UAT is done by the customer to see if the software matches their business requirements in a way that they understand. We will exclude any details about this level of testing. Let’s look at the first three levels of testing:

Unit tests

When the requirements are detailed enough, the developers start designing their architecture and decide about classes and their responsibilities, interfaces and so on. The QA Engineer(s) should involve in the design  discussions and question whether the design is suitable for automation – at this point they should request any necessary changes so that code can be unit tested easily. Let us assume that for this requirement, developers came up with 3 classes (unit testable) which will be used in the tile component.

  1. AmountValidator: This class is responsible for taking a number as amount, number of allowed decimal places. Then it decides if the amount has the allowed number of decimal places. We assume that number of decimal places is obtained from backend and it is the responsibility of Tile component.
  2. AmountFormatter: This class takes a number and a formatting standard and decides if the number is valid for required the formatting standard. For example, UK number formatting dictates that comma’,’ is a thousand separator and the ‘.’ is for decimal places. European standards are the opposite way around to UK formatting.
  3. GFAValidator: This class checks if a given number is between the minimum and the maximum allowed tradeable amounts.

Tile: This is a component and it has made up of several classes. The core class of tile(tile.js) has a property called ‘amount’ and the AmountFormatter, the AmountValidator,  and the GFAValidator classes are all applied to this property. This class is responsible for making the trade possible within an application.

Application: As well as other classes, it uses the tile class to enable user to login and execute FX trades by communicating with middleware (or server-side) systems.

Example of Unit Tests

AmountValidator, AmountFormatter and GFA validator are suitable candidates for low-level functionality testing. For brevity, the example below covers AmountValidator class only.

// isAmountValid() method in AmountValidator takes two arguments;
// first one is the (amount) as number
// and the second one is maximum allowed decimal points for a currency
// then it returns true or false.
//AmountValidatorTest.js file should contain test similar to below
AmountValidatorTest.prototype.setUp = function(){
var dps = 2;
var amountValidator = new AmountValidator();
}
AmountValidatorTest.prototype.testAmountIsValidWhenActualDpsIsLessThanMaxAllowedDps = function(){
assertTrue(amountValidator.isAmountValid(4000.5, 2));
}
AmountValidatorTest.prototype.testZerosAtEndAreIgnoredAsDecimalPlaces = function(){
assertTrue(amountValidator.isAmountValid(4000.500, 2));
}
AmountValidatorTest.prototype.testAmountIsNotValidWhenMaxDpsIsExceeded = function(){
assertFalse(amountValidator.isAmountValid(4000.567, 2));
}

Add more tests according to the table in Part I

Component Testing

We have tested each of the classes which are used by Tile.js with unit tests. Now we need to make sure that these classes are used in the Tile cmponent. One way of achieving this, is to test just one of the scenarios above (usually a happy path one) and check the behaviour of the tile. Please note that we are moving from testing implementation to testing behaviour of the component.

The tile is a component which should and can be tested in isolation. In Caplin we write the component tests in GWT (Given-When-Then) format using Js-test-driver and Jasmine framework. The example is below:

it("enables the buttons on a tile are after user entering a valid amount", function(){
given ("tile.new = true")
and ("tile.model.gfa.max = 4000");
and ("tile.model.gfa.min = 1");
and (tile.model.dps = 2");
when ("tile.model.amount.value = "3000.55");
then ("tile.model.executionButtons.sellButton.enabled = true");
and ("tile.model.executionButtons.buyButton.enabled = true");
});
it("updates the amount after user entering a valid amount from browser", function(){
given ("tile.new = true");
when ("tile.view(.amount .input .native).innerHTML = '2,000.75'");
then ("tile.model.amount.value = '2,000.75'");
});


The second test for checking the correct binding between model and view (see article Automated testing Caplin Solution)

Integration Testing

At this level we need to think about the big picture. The user enters an amount so that s\he can trade using the tile. We want to make sure that when the user trades, the correct amount is sent to the backend systems and we can prove it did.

The tile is part of an application. The application has a tile or means of creating more tiles, a panel which holds these tiles and a blotter where all user successful trades are displayed. We are going to implement selenium web driver framework for integration testing. It will be something like:

     public void setUp() {
WebDriver driver = new InternetExplorer();
driver.start("localhost:8080/myapp/application.jsp");
//More code for logging into the app goes here
}
@test
public void testUserExecutesTileTradeSuccessfully() {
String amount = "2000"
WebElement tile = driver.findElement(By.className("tile"));
WebElement amountInput = driver.findElement(By.cssSelector(".amount .input"));
amountInput.sendKeys(amount);
//more code here to wait until button is enabled
WebElement button = tile.findElement(By.className("buyButton"));
button.click();
WebElement blotter = driver.findElement(By.className("blotter"));
WebElement topRow = blotter.findElement(By.className("topRow"));
// more code to wait data comes back from server side
WebElement amountCell = topRow.findElement(By.cssSelector(".amount .span"));
String amountFromBlotter = amountCell.getText();
assertEquals(amountFromBlotter, amount);
}

Conclusion

We have had lots of tests at unit level, just a few tests at component level and finally just one test at integration level. We still need to perform exploratory & usability testing to ensure that the system is robust and there is no unexpected behaviour which is not covered by business requirements. For example, what happens if connection is lost halfway through the trade?

The QA engineers will not have enough time for exploratory testing if these automated test cases are not automated, because they will spend all their time before each release manually testing the scenarios mentioned above. This will eventually lead to a low-quality product which kind of works but not robust.

Automated tests both facing the business (component, integration) and the team (component, unit) are essential in the Agile testing practice.

Leave a Reply

Your e-mail address will not be published. Required fields are marked *