现状:你是不是还在为自己的TestCase代码杂乱无章而苦恼,咎其根本还在于针对不同的用例,输入参数和mock信息的组装全部作为你的程序代码分布在各个单元测试程序中。
期望:因此为了让测试程序更加优雅的显示作为code的本质,我们需要把输入参数和mock内容与程序本身尽可能的达到松耦合的布局,即程序归程序,用例数据归用例数据。
如何:我们怎么来完成这样的一个分离动作呢,下面讲讲本人实现的基本思路。利用JUNIT4中的参数化测试为基础,通过解析文件来初始化参数信息,并对用例提供注销控制。
下面我们着重介绍下实现BaseTestCase.
@RunWith(Parameterized.class)
@ContextConfiguration(locations = {"XXX.xml"})
public class BaseTestCase extends AbstractJUnit4SpringContextTests{
/** 用例ID */
protected String caseId;
/** 描述 */
protected String description;
/** 期望值 */
protected String expectValue;
/** 测试上下文管理 */
private final TestContextManager testContextManager;
public BaseTestCase(){
this.testContextManager = new TestContextManager(getClass());
this.caseId = caseId;
this.description = description;
this.expectValue = expectValue;
}
/**
* 依靠<code>DependencyInjectionTestExecutionListener<code>完成注入上下文信息
* @throws Throwable
*/
@Before
public void injectDependencies() throws Throwable {
this.testContextManager.prepareTestInstance(this);
}
/**
* 获取用例数据
* @param caseFilePath
* @return
*/
protected static Collection<String[]> loadCaseData(String caseFilePath) {
if (StringUtil.isBlank(caseFilePath)) {
throw new IllegalArgumentException("未初始化用例文件信息");
}
List<String[]> caseDataList = FileParser.loadItems("cases/" + caseFilePath);
if (CollectionUtils.isEmpty(caseDataList)) {
throw new IllegalArgumentException("准备数据不能为空");
}
// 剔除第一行标题信息
caseDataList.remove(0);
return caseDataList;
}
}
由于在自己基类使用的runner为Parameterized,与使用SpringJUnit4ClassRunner的不同在于少了自动对测试上下文进行依赖注入关联,因此我们需要自己手工触发完成,大家在类源码里可以注意到这一点。主要还是依赖于DependencyInjectionTestExecutionListener完成。同时由于测试基类集成了AbstractJUnit4SpringContextTests了,在此类的源码里可以看到两个关键点DependencyInjectionTestExecutionListener和其中的ApplicationContext,因此我们在子类里不需要再自己写什么上下文的属性,也不需要再添加依赖注入的执行监听器。
下面再看下FileParser的实现
/**
* <p>文件解析器</p>
* @author Eric.fu
*/
public class FileParser {
/** 分隔符 */
private static final String SPLIT_TAG = ",";
/** 忽略标记 */
private static final String IGNORE_TAG = "#";
/**
* 获取文件内容
* @param filePath
* @return
*/
public static List<String[]> loadItems(String filePath) {
List<String[]> itemList = new ArrayList<String[]>();
ClassPathResource resource = new ClassPathResource(".");
BufferedReader reader = null;
try {
String path = resource.getFile().getAbsolutePath();
File file = new File(path + "/META-INF/" + filePath);
reader = new BufferedReader(new FileReader(file));
String tempString = null;
// 一次读入一行,直到读入空为文件结束
while (StringUtil.isNotBlank(tempString = reader.readLine())) {
if (tempString.indexOf(IGNORE_TAG) == 0) {
continue;
}
itemList.add(tempString.split(SPLIT_TAG));
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e1) {
}
}
}
return itemList;
}
}
此类为相当普通的一段读文件的代码,这里有个特殊点在于加了一个忽略标记,用于注释用例数据,即把不跑的数据暂时注释掉,不作为读入参数。
下面再来看下一个简单的测试子类实现
public class SubTestCase extends BaseTestCase {
/** 测试请求 */
protected String request;
/**
* 构造入款支付清算
*/
public SubTestCase(String caseId, String description, String expectValue, String request){
super(caseId, description, expectValue);
this.request = request;
}
@Parameters
public static Collection<String[]> caseData() {
return loadCaseData("case.csv");
}
/**
* 执行
*/
@Test
public void execute() {
Assert.notNull(request);
}
}
在子类里通过构造方法初始化参数,参数数据来源为case.csv文件。
文件格式为caseId,description,expectValue,request
本文主要介绍的利用JUNIT4中的参数化测试来完成程序与测试数据的分离,当然如果你用的testNG,则可以用更优雅的provider来完成此项工作。在测试用例中除了让数据与程序分离还是不够的,还需要将mock部分也分离出去,这样才能更好的达到扩展的效果。