前段时间做项目对整接口做集成测试,使用meavn+spring+testng+mybatis+dbunit对接口进行测试。
1先测试一个普通的树结构加载,不带参数,从数据库中读取树结构。
public interface TreeService { Map<String, Object> getTree(); }
testng提供接口AbstractTestNGSpringContextTests,可以将项目配置文件导入。
@ContextConfiguration(locations = {"classpath*:applicationContext.xml","classpath*:mybatis-config.xml"}) public class TreeServiceImplTest extends AbstractTestNGSpringContextTests{ Map<String, Object> 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<String, Object> promap = new HashMap<String, Object>(); 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<String, Object> promap = new HashMap<String, Object>(); 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); } }