前段时间做项目对整接口做集成测试,使用meavn+spring+testng+mybatis+dbunit对接口进行测试。
1先测试一个普通的树结构加载,不带参数,从数据库中读取树结构。
public interface TreeService {
Map getTree();
}
testng提供接口AbstractTestNGSpringContextTests,可以将项目配置文件导入。
@ContextConfiguration(locations = {"classpath*:applicationContext.xml","classpath*:mybatis-config.xml"})
public class TreeServiceImplTest extends AbstractTestNGSpringContextTests{
Map map;
String retStr = "";
@Autowired
private TreeService treeservice;
@Test(threadPoolSize = 3, invocationCount = 2, timeOut = 100000)
public void getTree() {
map = treeservice.getTree();
retStr = JSONObject.toJSONString(map).toString();
System.out.println(retStr);
Assert.assertNotNull(retStr);
Assert.assertEquals("{\"count\":10,\"data\":\"123\",\"respCode\":1,\"respDesc\":\"test1234\"}", retStr);
}
}
2更新函数的测试中需要向数据库中写入更新的参数,这个每次更新都会对数据库进行改变,很难保证第一次测试的操作会不会影响到第二次测试,所以使用dbunit完成数据库的备份和恢复。dbunit是一个基于junit扩展的数据库测试框架。它提供了大量的类对与数据库相关的操作进行了抽象和封装。为依赖于其他外部系统(如数据库或其他接口)的代码编写单元测试是一件很困难的工作。在这种情况下,有效的单元必须隔离测试对象和外部依赖,以便管理测试对象的状态和行为。使用mock object对象,是隔离外部依赖的一个有效方法。如果我们的测试对象是依赖于DAO的代码,mock object技术很方便。如果测试对象变成了DAO本身或者集成测试,就需要对实际的数据库进行操作。
public interface AppRankService {
boolean updateAppRankById(String json,String id);
}
使用dbunit的时候先封装两个方法一个备份数据库,一个回滚,因为数据库中存在null,而dbunit认为null中数值为错误,所以需要配置忽略空值。
public class DbUnitService extends DBTestCase {
private final Logger log = Logger.getLogger(getClass());
String dir_name = "dbbackup";//备份到文件夹
//配置测试库
public DbUnitService() {
String dbusername = "root";
String dbpassword = "root";
String dbtype = "mysql";
String dburl = "jdbc:mysql://192.168.12.163:3306/mquery_test?characterEncoding=UTF-8";
createDir(dir_name);
if (dbtype.equals("mysql")) {
System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_DRIVER_CLASS, "com.mysql.jdbc.Driver");
} else {
log.error("undefined db type !");
}
System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_CONNECTION_URL, dburl);
System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_USERNAME, dbusername);
System.setProperty(PropertiesBasedJdbcDatabaseTester.DBUNIT_PASSWORD, dbpassword);
}
//备份单张表
public void backupTable(String tbname, String xmlFileName) throws Exception {
IDatabaseConnection connection = getConnection();
try {
QueryDataSet dataSet = new QueryDataSet(connection);
dataSet.addTable(tbname);
File f_file = new File(dir_name + File.separator + xmlFileName);
FlatXmlDataSet.write(dataSet, new FileOutputStream(f_file));
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (connection != null)
connection.close();
} catch (SQLException e) {
}
}
}
//回滚操作
public void rollback(String xmlFileName) throws Exception {
IDatabaseConnection connection = getConnection();
connection.getConfig().setFeature(DatabaseConfig.FEATURE_ALLOW_EMPTY_FIELDS, true);
try {
FlatXmlDataSetBuilder builder = new FlatXmlDataSetBuilder();
builder.setColumnSensing(true);
IDataSet ds = builder.build(new FileInputStream(new File(dir_name + File.separator + xmlFileName)));
// recover database
DatabaseOperation.CLEAN_INSERT.execute(connection, ds);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (connection != null)
connection.close();
} catch (SQLException e) {
}
}
}
}
测试用例中使用两种方式,一种用sql语句进行精确校验,使用testng的断言函数比较查询结果和期望值,另外一种使用dbunit的Assertion.assertEquals函数比较两个表,在更新操作后,首先读取数据存入一个临时表dbTable中,然后从xml文件中读取期望结果的表存入xmlTable中,然后对比两个表,可以使用includedColumnsTable包含某些列,也可以使用excludedColumnsTable排除某些列后比较剩下的列。两种方式各有优势,sql语句的方式更灵活一些,Assertion需要对整个表的行数,和某列进行对比,所以占用资源更多,但是某些dbunit官方认为对数据库操作的时候需要校验整个表的内容,以保证本次操作不对额外的行破坏。
@ContextConfiguration(locations = {"classpath*:applicationContext.xml","classpath*:mybatis-config.xml"})
public class AppRankServiceImplTest extends AbstractTestNGSpringContextTests {
public String app_link_ip;
public int rank;
public String id;
public String app_id;
DatabaseService ds = new DatabaseService();
Connection conn = null;
DbUnitService dbunit = new DbUnitService();
@Autowired
private AppRankService appRankService;
@Test(threadPoolSize = 10, invocationCount = 20, timeOut = 100000)
public void updateAppRankById() throws Exception {
HashMap promap = new HashMap();
long id = Thread.currentThread().getId();
promap.put("app_link_ip", "test_dbunit");
promap.put("rank", ranknum++);
promap.put("app_link_domain", uuidstr);
String testStr = JSONObject.toJSONString(promap).toString();
//执行被测方法,updateAppRankById操作数据库
boolean retboolen = appRankService.updateAppRankById(testStr, "b3b3310f0e0a466893c39cfc431bcac2");
Assert.assertTrue(retboolen);//检查是否可以成功执行update操作
String sql = "SELECT app_link_ip FROM app_rank_source WHERE id = \"b3b3310f0e0a466893c39cfc431bcac2\"";
String retstr = ds.getData(conn, sql, 1, 1);//通过sql语句检查更新字段是否成功被改变
Assert.assertEquals(retstr, "test_dbunit");
}
@Test(threadPoolSize = 10, invocationCount = 20, timeOut = 100000)
public void updateAppRankById2() throws Exception {
HashMap promap = new HashMap();
promap.put("app_link_ip", "test_dbunit");
promap.put("rank", 1);
String testStr = JSONObject.toJSONString(promap).toString();
boolean retboolen = appRankService.updateAppRankById(testStr, "b3b3310f0e0a466893c39cfc431bcac2");
Assert.assertTrue(retboolen);//判断是否执行成功
//从真实表app_rank_source中读取数据存在临时表dbTable中
IDataSet dbDataSet = dbunit.getDBDataSet();
ITable dbTable =dbDataSet.getTable("app_rank_source");
//从test_resource中获取期望结果,期望结果存储在xml中,读取,放到临时表xmlTable中
IDataSet xmlDataSet =dbunit.getXmlDataSet("app_rank_source.xml");
ITable xmlTable = replacementDataSet.getTable("app_rank_source");
//比较某列或几列,或者使用excludedColumnsTable排除某列
dbTable=DefaultColumnFilter.includedColumnsTable(dbTable, new String[]{"app_link_ip"});
xmlTable =DefaultColumnFilter.includedColumnsTable(xmlTable, new String[]{"app_link_ip"});
// Assert.assertEquals(dbTable.getRowCount(),xmlTable.getRowCount());//比较行数
Assertion.assertEquals(dbTable, xmlTable);
}
@BeforeTest
public void beforeClass() throws Exception {
conn = ds.connectDBDriver("mysql", "root", "root",
"jdbc:mysql://192.168.12.163:3306/mquery_test?characterEncoding=UTF-8");
dbunit.backupTable("app_rank_source","tables.xml");//备份表
}
@AfterTest
public void afterClass() throws Exception {
dbunit.rollback("tables.xml");//还原表
ds.closeDBDriver(conn);
}
}