testng被普遍使用于基于java和spring的系统结构中,用于保证系统功能,本身testng的特点:
1.结构清晰
2.支持多种数据源
3.可与maven集成
4.环境/数据准备方便
可用于系统中对外提供的接口进行接口测试脚本的编写(单元测试则一般用junit完成)。
经典的测试脚本,一般分为三个步骤:
1.数据和环境初始化;
2.执行被测接口调用;
3.断言判断。
负责任和健壮的测试脚本,一般还要进行测试数据初始化,测试依赖的环境的调整(如业务开关打开),在执行完测试脚本后,进行数据的清理,如环境的恢复和测试遗留数据的清理。
下面记录一个简单的接口测试编写过程:
被测接口:
package net.test.testng.service;
/**
* interface to be tested.
* @author T0DD
*
*/
public interface ToBeTestedService {
/**
* return a string.
* @return
*/
public String returnString();
/**
* return a obj
* @return
*/
public Object returnObject();
}
被测接口实现:
package net.test.testng.service.impl;
import java.sql.SQLException;
import net.test.testng.model.Result;
import net.test.testng.service.DataBaseDAO;
import net.test.testng.service.FunctionSwitch;
import net.test.testng.service.ToBeTestedService;
public class ToBeTestedServicezImpl implements ToBeTestedService {
protected DataBaseDAO dataBaseDAO;
protected FunctionSwitch functionSwitch;
@Override
public String returnString() {
return "a string";
}
@Override
public Object returnObject() {
functionSwitch.setAvaiable(true);
Object queriedData = null;
try {
queriedData = dataBaseDAO.readData("select * from test db;");
} catch (SQLException e) {
return new Result(false, "DB error.", null);
}
return new Result(true, "Switch turned on and queried successed.",
queriedData);
}
}
其中两个方法,一个非常简单的方法,returnString,只返回一个“a string";另一个,根据业务开关代开与否,进行数据库的读操作,组装result对象返回,result定义为:
package net.test.testng.model;
public class Result {
public boolean isSuccess;
public String errorMsg;
public String succesMsg;
public Object data;
public Result(boolean isSuccess, String msg, Object data) {
super();
this.isSuccess = isSuccess;
if (isSuccess) {
this.succesMsg = msg;
} else {
this.errorMsg = msg;
}
this.data = data;
}
public boolean isSuccess() {
return isSuccess;
}
public void setSuccess(boolean isSuccess) {
this.isSuccess = isSuccess;
}
public String getErrorMsg() {
return errorMsg;
}
public void setErrorMsg(String errorMsg) {
this.errorMsg = errorMsg;
}
public String getSuccesMsg() {
return succesMsg;
}
public void setSuccesMsg(String succesMsg) {
this.succesMsg = succesMsg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
}
重点,测试类
package net.test.testng.servicetest;
import java.sql.SQLException;
import net.test.testng.model.Result;
import net.test.testng.service.DataBaseDAO;
import net.test.testng.service.FunctionSwitch;
import net.test.testng.service.ToBeTestedService;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
public class ToBeTestedServiceTest {
protected FunctionSwitch functionSwitch;
protected DataBaseDAO dataBaseDAO;
protected boolean env;
protected ToBeTestedService toBeTestedService;
@BeforeClass
public void initEnv() {
env = functionSwitch.isAvaiable();
if (functionSwitch.setAvaiable(true)) {
Assert.fail("Prepare environment error!");
}
}
@BeforeMethod
public void initData() {
try {
dataBaseDAO.insert("insert into db data...");
} catch (SQLException e) {
Assert.fail("Init database error!");
}
}
@Test
public void testReturnString() {
String result = toBeTestedService.returnString();
Assert.assertNotNull(result);
Assert.assertEquals(result, "a string");
}
@Test
public void testReturnObject() {
Object result = toBeTestedService.returnObject();
Assert.assertNotNull(result);
if (!(result instanceof Result)) {
Assert.fail("Error return type.");
}
Assert.assertTrue(((Result) result).isSuccess());
Assert.assertNotNull(((Result) result).getSuccesMsg());
Object dbData = null;
try {
dbData = dataBaseDAO.readData("insert into db data...");
} catch (SQLException e) {
Assert.fail("read data from db error.");
}
Assert.assertEquals(((Result) result).getData(), dbData);
}
@AfterMethod
public void cleanData() {
try {
dataBaseDAO.delete("insert into db data...");
} catch (SQLException e) {
Assert.fail("Delete database error!");
}
}
@AfterClass
public void restoreEnv() {
functionSwitch.setAvaiable(env);
}
}
说明:
1.其中beforeClass注解下,为整个test case运行所需要的基本环境,包括业务开关,上下游业务中所需要的其他服务的引入以及服务引入成功的判断,如果有一个服务都没有引入成功,其实该test case就没有必要执行,也就不需要浪费测试资源去执行了。
2.beforeMethod注解下,可以提供本次测试方法(服务中有两个方法,其中一个就依赖了数据库中的内容,所以要初始化数据库。)所依赖的环境信息。
3.两个after中,依次对数据和环境进行恢复,在CI情况下,本次执行的结果 有可能会影响到下次执行,所以,负责任的方法是,执行完本次test case后,就相应的进行环境和数据的清理,便于下次接口测试的顺利执行。
建议:
1.接口测试脚本的结构很重要,对于业务场景相似的情况,可以设置测试基类,将环境/数据的初始化放置在基类中;将测试执行和测试结果判断适当加以区分,这样逻辑会清晰,便于测试脚本维护。
2.提高脚本复用程度,如采取参数化驱动,使用csv/txt/excel构建测试数据驱动,编写自己的dataProvider,解析测试数据,提高脚本的利用率,如上述脚本中,针对returnObject方法,可以构建
a.数据初始化另一个返回结果
b.数据初始化为空
c.业务开关关闭
等情况,并且测试数据和测试结果一一对应,都进行参数化,加上正常场景,相当于进行了4个业务场景的测试,能够极大减少回归测试的人工。
3.为脚本执行后的副作用负责,主要是指,在脚本执行的过程中,难免会引入一些测试数据和对当前业务流的改动,在执行完成后,需要将这些执行痕迹清理和还原,一是便于手工测试的正常执行,而是为下次持续集成,减轻了错误概率。