我们在前面的文章中,和大家分享过接口自动化测试一些基本的实现方法,但是,你很快就会发现,如果在测试脚本中硬编码测试数据的话,测试脚本灵活性会非常低。而且,对于那些具有重复的请求,而只是测试入参不同的用例来说,就会存在大量重复的代码。
那么怎么把自己从简单、重复的工作中解放出来呢?
这个时候我们应考虑把测试数据和测试脚本分离,也就是说数据驱动。
测试脚本中通过 data provider
去数据源中读取一行数据,赋值给相应的变量,执行用例。接着再去文件中读取下一行数据,读取完所有的数据后,测试结束。参数化文件中有几行数据,测试用例就会被执行几次。如图所示:
我们可以在每个测试方法上使用任意数量的参数,并指示 TestNG 使用 @Parameters
注释传递正确的参数。
TestNG有两种方法可以设置这些参数(@Factory 数据工厂不在此介绍):
如果简单参数,则可以在 testng.xml 中指定它们,在以下代码中,我们指定的参数 name 和 age 值。 此 XML 参数在 testng.xml 中 定义:
<suite name="parameter">
<test name="param">
<parameter name="name" value="zhangsan"/>
<parameter name="age" value="10"/>
<classes>
<class name="com.zuozewei.springboottestngdatadrivendemo.paramter.ParamterTest"/>
classes>
test>
suite>
测试方法将分别接收参数 name 和 age 的值。
@Slf4j
public class ParamterTest {
@Test
@Parameters({"name","age"})
public void paramTest(String name,int age){
log.info("name = [{}] ; age = [{}]" ,name,age);
}
}
注意 @Parameters 可以被放置在下列位置:
@Parameters({ "name", "age" })
@BeforeMethod
public void beforeTest(String name, String age) {
m_name = name; // 查询数据源值
m_age = age;
}
注意:
如果需要传递复杂参数或需要从 Java 创建的参数(复杂对象,从文件或数据库读取的对象等等),则在 testng.xml 中指定参数可能不够。在这种情况下,可以使用数据提供程序提供测试所需的值。数据提供程序是类上的一个方法,它返回一组对象数组。此方法使用 @DataProvider 注释。
@DataProvider函数,需要定义属性 name:
@DataProvider(name="data")
public Object[][] providerData(){
Object[][] objects = new Object[][]{
{"zhangsan",10},
{"lisi",20},
{"wangwu",30}
};
return objects;
}
@Test 测试用例,属性 dataProvider 需要指定对应的数据提供者名称。
@Test(dataProvider = "data")
public void testDataProvider(String name,int age){
log.info("name = [{}] ; age = [{}]" ,name,age);
}
执行结果:
name = [zhangsan] ; age = [10]
name = [lisi] ; age = [20]
name = [wangwu] ; age = [30]
===============================================
Default Suite
Total tests run: 3, Failures: 0, Skips: 0
===============================================
@DataProvider 函数可以插入 Method 和 ITestContext 类型参数,这两个参数里面可以获取很多有用的信息。
@DataProvider函数:
@DataProvider(name="methodData")
public Object[][] methodDataTest(Method method){
Object[][] result=null;
if(method.getName().equals("test1")){
result = new Object[][]{
{"zhangsan",20},
{"lisi",25}
};
}else if(method.getName().equals("test2")){
result = new Object[][]{
{"wangwu",50},
{"zhaoliu",60}
};
}
return result;
}
@Test 测试执行脚本:
@Test(dataProvider = "methodData")
public void test1(String name,int age){
log.info("test111方法: name = [{}] ; age = [{}]" ,name,age);
}
@Test(dataProvider = "methodData")
public void test2(String name,int age){
log.info("test222方法: name = [{}] ; age = [{}]" ,name,age);
}
执行结果:
test111方法: name = [zhangsan] ; age = [20]
test111方法: name = [lisi] ; age = [25]
test222方法: name = [wangwu] ; age = [50]
test222方法: name = [zhaoliu] ; age = [60]
===============================================
Default Suite
Total tests run: 7, Failures: 0, Skips: 0
===============================================
有的场景我们需要大量参数进行读取,比如参数数据源是 DB,而数据达到百万级,这样测试程序遍历所有数据时,可能就会导致内存溢出。
那么我们怎样解决这个问题?当我们获取了一条数据,对它执行测试方法,然后就废弃这个数据对象,再测试下一个。这个原则是延迟初始化,这个思想就是当你真正需要一个对象时才创建它,而不是提前创建它。
为了实现这种方法,TestNG 允许我们从数据提供者返回一个 Iterator 对象,而不是一个二维对象数组。
Iterator 是 java.util 包中的一个接口,它的方法签名如下:
public interface Iterator<E> {
boolean hasNext();
E next();
default void remove();
}
它可以通过 next 调用下一组数据,这样就有机会在最后一刻实例化相应的对象,即刚好在需要在这些参数的测试方法被调用之前。
下面例子是重写后的例子,我们实现了一个 Iterator,它将返回 4 个带有不同ID的对象:
public class AccoutIterator implements Iterator {
private int index =0;
static private final int MAX =4;
@Override
public boolean hasNext() {
return index < MAX;
}
@Override
public Object next() {
return new Object[]{
//这里就是放入要实现的对象或者一组数据
"延迟数据提供:"+ (index++)
};
}
@Override
public void remove() {
throw new UnsupportedOperationException("remove");
}
@DataProvider函数调用:
@DataProvider(name = "iterator")
public Iterator<Object[]> iteratorDataProvider(){
return new AccoutIterator();
}
@Test测试运行函数:
@Test(dataProvider = "iterator")
public void testcase2(String name){
log.info(" name = [{}] " ,name);
}
运行结果:
name = [延迟数据提供:0]
name = [延迟数据提供:1]
name = [延迟数据提供:2]
name = [延迟数据提供:3]
===============================================
Default Suite
Total tests run: 4, Failures: 0, Skips: 0
===============================================
数据提供程序可以与并行属性并行运行:
@DataProvider(parallel = true)
// ...
从 XML 文件运行的并行数据提供程序共享相同的线程池,默认情况下大小为 10。可以在 XML 文件的 suite 标记中修改此值:
<suite name="Suite1" data-provider-thread-count="20" >
如果要在不同的线程池中运行几个特定的数据提供程序,则需要从其他 XML文件运行它们。
这篇的知识点:
当然,DataProvider 只是从行为操作上分离了数据的提供方式,没有从根本上解决自动化测试中测试数据本身的稳定性、快速响应变化、数据丢失、数据被修改的这些难点和阻碍:
自动化测试的其他方面都不是什么大问题,最主要的阻碍就是测试数据本身(特别是在真实的测试环境上时)
本文示例源码:
https://github.com/zuozewei/Java-API-Test-Examples/tree/master/springboot-testng-data-driven-demo
参考资料:
[1]:https://blog.csdn.net/LangSand/article/details/53895654
[2]:https://testng.org/doc/index.html
[3]:软件测试52讲 茹炳晟