Monday, January 2, 2012

A simple Test Framework with example

It’s a very common requirement to have a testing framework of its own for an application to provide a capability that’s common to all the unit classes. Say, if you are building test classes to test the Rest based webservices you would like to have a framework that provides the capability to provide the creation of request in the form of URLs. The response can be either xml or json and we can provide our own implementation to validate the response that’s come in, so that for each unit test of services we don’t have to write a code in each test class to validate the response. I am suggesting a simple framework that provides the backbone for such a framework implementation. It’s not specific to a particular architecture but can be modified to use it for any architecture that user wants.

I am suggesting a kind of driver class to run all the tests. The driver class will be responsible to provide specific behaviors to the whole test application. Suppose you want to add the facility of creating the url ,as described in the above example for the rest based webservice, of the resource. You need to add that method or operation in this driver class.

Coming on to Driver class.

Our driver class is that of TestCase. It has two abstract methods ;
protected abstract void executeTest() throws Exception;
protected abstract void checkActualResults() throws Exception;
which will be extended by every TestCase to have the specific behavior of the application. The other class will be that of AllTests which uses the junit test suites to run all the tests.

I am giving the code for the TestCase and AllTest below.

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;


public abstract class TestCase {

private String testName = "";

protected Map testConditions = new HashMap();
private Map expectedResults = new HashMap();
private Map actualResults = new HashMap();

public TestCase() {
}


public boolean isTestPass() throws Exception {
System.out.println("Test Case: " + this.getTestName());

this.executeTest();
this.checkActualResults();
this.syncResults();

boolean isMatch = this.isResultsMatch();
System.out
.println((isMatch ? "PASS: " : "FAIL: ") + this.getTestName());
return isMatch;
}


protected abstract void executeTest() throws Exception;


protected abstract void checkActualResults() throws Exception;


private boolean isResultsMatch() {
Iterator i = expectedResults.entrySet().iterator();
Map.Entry result;
while (i.hasNext()) {
result = (Map.Entry) i.next();
if (this.getExpectedResult(result.getKey()) != this
.getActualResult(result.getKey()))
return false;
}
return true;
}


private void syncResults() {
int exp = expectedResults.size();
int act = actualResults.size();
if (exp == act)
return;
Map.Entry result;
if (exp > act) {
Iterator i = expectedResults.entrySet().iterator();
while (i.hasNext()) {
result = (Map.Entry) i.next();
if (!actualResults.containsKey(result.getKey())) {
actualResults.put(result.getKey(), false);
}
}
} else {
Iterator i = actualResults.entrySet().iterator();
while (i.hasNext()) {
result = (Map.Entry) i.next();
if (!expectedResults.containsKey(result.getKey())) {
expectedResults.put(result.getKey(), false);
}
}
}
}

// reset all test conditions, and test results
public void initialize() {
testConditions.clear();
expectedResults.clear();
actualResults.clear();
}


public void setAllConditions(Object value) {
this.setMapValue(this.testConditions, value);
}


public void setAllExpectedResults(boolean value) {
this.setMapValue(this.expectedResults, value);
}

private void setMapValue(Map m, Object value) {
Iterator i = m.entrySet().iterator();
Map.Entry entry;
while (i.hasNext()) {
entry = (Map.Entry) i.next();
m.put(entry.getKey(), value);
}
}


public void setTestCondition(String key, Object value) {
testConditions.put(key, value);
}

public Object getTestCondition(String key) {
return testConditions.get(key);
}

public void setExpectedResult(String key, Boolean value) {
expectedResults.put(key, value);
}

public Boolean getExpectedResult(String key) {
return expectedResults.get(key);
}

public void setActualResult(String key, Boolean value) {
actualResults.put(key, value);
}

public Boolean getActualResult(String key) {
return actualResults.get(key);
}

public String getTestName() {
return testName;
}

public void setTestName(String testName) {
this.testName = testName;
}

}

AllTest will look like


import org.junit.runner.RunWith;
import org.junit.runners.Suite;
import org.junit.runners.Suite.SuiteClasses;


@RunWith(Suite.class)

@SuiteClasses({LoginServiceTest.class })

public class AllTests {
}

An example of using this:

We have LoginService like this

public class LoginService {

public boolean isValid(String username, String password) {

if (username.equals("kanishka") && password.equals("vatsa"))
return true;
return false;
}

}

We develop the TestCase like this :

public class LoginServiceTestCase extends TestCase {


private LoginService loginService = new LoginService();
protected final String CONDITION_USERNAME = "userName";
protected final String CONDITION_PASSWORD = "password";

protected final String RESULT_IS_ERROR = "isError";

private boolean isError;

protected void checkActualResults() throws Exception {
setActualResult(RESULT_IS_ERROR, isError);
}

protected void executeTest() throws Exception {
try {
String userName = (String) getTestCondition(CONDITION_USERNAME);
String password = (String) getTestCondition(CONDITION_PASSWORD);
isError = !loginService.isValid(userName, password);
} catch (Exception e) {
isError = true;
}
}

}

We write the junit test case like :

public class LoginServiceTest {


LoginServiceTestCase loginTestCase = new LoginServiceTestCase();

@Before
public void setup() {
System.out.println(" in setup");
loginTestCase.initialize();
}

@After
public void teardown() {
}

@Test
public void testLoginWithValidAccount() throws Exception {
loginTestCase.setTestName("Validate Login with valid credentials");
loginTestCase.setTestCondition(loginTestCase.CONDITION_USERNAME,
"kanishka");
loginTestCase.setTestCondition(loginTestCase.CONDITION_PASSWORD, "vatsa");
loginTestCase.setExpectedResult(loginTestCase.RESULT_IS_ERROR, false);
assertTrue(loginTestCase.isTestPass());
}

@Test
public void testLoginWithEmptyCredentials() throws Exception {
loginTestCase.setTestName("Validate Login with empty credentials");
loginTestCase.setTestCondition(loginTestCase.CONDITION_USERNAME, "");
loginTestCase.setTestCondition(loginTestCase.CONDITION_PASSWORD, "");
loginTestCase.setExpectedResult(loginTestCase.RESULT_IS_ERROR, true);
assertTrue(loginTestCase.isTestPass());
}

@Test
public void testLoginWithInvalidCredentials() throws Exception {
loginTestCase.setTestName("Validate Login with invalid credentials");
loginTestCase.setTestCondition(loginTestCase.CONDITION_USERNAME,
"user");
loginTestCase.setTestCondition(loginTestCase.CONDITION_PASSWORD,
"password");
loginTestCase.setExpectedResult(loginTestCase.RESULT_IS_ERROR, true);
assertTrue(loginTestCase.isTestPass());
}
}

In the loginServiceTest class we are encapsulating the LoginTestCase instance hence we are delegating the responsibility to provide the application specific behavior(in our example the restful behavior) to the TestCase instance which will provide the behavior.

Hope you will like this simple but useful implementation. Looking forward for your comments.

Happy coding :)

No comments: