51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

手机号码,快捷登录

查看: 2258|回复: 2
打印 上一主题 下一主题

TestNG 通用数据驱动

[复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2018-3-2 16:07:46 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
动机

TestNG 是一个非常优秀的框架,功能丰富、简单易用,但是它的 DataProvider 用起来确实有点蛋
疼:不支持外部数据源(得自己写读取的代码),散落在各处的 DataProvider 方法,无限重复的
return new Object[] {} 语句,以及万一手滑返回值和参数不对应就抛出的异常实在是令人发狂,
于是天真的我开始幻想这些能不能自动完成呢?答案是当然能!好了,闲言少叙,这里要实现的
就是标题说提到的——TestNG 通用化数据驱动,具体如下:

所有代码统一使用同一个 DataProvider 方法
DataProvider 方法根据测试方法参数列表自动判断返回值类型和个数
支持行过滤
支持外部数据源
支持并发
原理

其实核心原理很简单,就是根据反射机制获取当前测试方法,然后遍历参数列表,根据不同的类型
返回对应的值即可

结构设计

要支持不同格式的外部数据源首先要定义一个统一的数据格式,此处命名为 IRow,然后再定义一
个接口 IReader,负责从外部读入数据并转换为 IRow
  1. public interface IRow {

  2.     int getIndex();

  3.     String[] getColumnNames();

  4.     int getColumnNumbers();

  5.     boolean getBoolean(String key);

  6.     boolean getBoolean(int index);

  7.     // 省略部分方法

  8.     String getString(String key);

  9.     String getString(int index);

  10. }

  11. public interface IDataReader {

  12.     void changeGroup(String group);

  13.     IRow readRow();

  14.     void close();

  15. }
复制代码
为了保证不同测试方法的数据相互独立,需要引入一个数据分组的概念,类似于 Excel 中的 sheet
或者数据库中 table,因此再定义一个接口 IGroupResolver 用于根据当前测试方法解析出其对应的
数据分组名
  1. public interface IGroupResolver {

  2.     String resolve(Method method);

  3. }
复制代码
定义支持的参数类型,遍历参数列表生成 Object[][];此处可以简单的使用 if-else 语句或者 switch
语句,但是为了提高扩展性,我使用了组合模式(我不会告诉你这个是我从 SpringMVC 框架的
HandlerMethodArgumentResolverComposite 类抄的 😆)
  1. public class ArgumentResolverComposite implements IArgumentResolver {
  2.     private List<IArgumentResolver> mResolvers = new LinkedList<>();
  3.     private Map<Parameter, IArgumentResolver> mResolverCache = new HashMap<>();

  4.     @Override
  5.     public boolean support(Parameter param) {
  6.         return getResolver(param) != null;
  7.     }

  8.     @Override
  9.     public Object resolve(Parameter param, IRow row) {
  10.         IArgumentResolver vResolver = getResolver(param);
  11.         if (vResolver == null) {
  12.             throw new IllegalArgumentException("Unknown parameter type [" + param.getType().getName() + "]");
  13.         }
  14.         return vResolver.resolve(param, row);
  15.     }

  16.     public ArgumentResolverComposite addResolver(IArgumentResolver resolver) {
  17.         if (resolver != null)
  18.             mResolvers.add(resolver);
  19.         return this;
  20.     }

  21.     private IArgumentResolver getResolver(Parameter param) {
  22.         IArgumentResolver vResolver = mResolverCache.get(param);
  23.         if (vResolver == null) {
  24.             for (IArgumentResolver r : mResolvers) {
  25.                 if (r.support(param)) {
  26.                     vResolver = r;
  27.                     mResolverCache.put(param, r);
  28.                     break;
  29.                 }
  30.             }
  31.         }
  32.         return vResolver;
  33.     }

  34. }
复制代码
为了支持并发需要在 IDataReader 上动手脚,考虑到各个线程间彼此独立,使用 ThreadLocal 最合
适不过了,为了减少重复代码,搞一个 AbstractReader 定义 ThreadLocal 的相关存取操作
  1. public abstract class AbstractReader<T> implements IDataReader {
  2.     private ThreadLocal<T> mData = new ThreadLocal<>();

  3.     @Override
  4.     public void changeGroup(String group) {
  5.         T vLocal = getLocalData();
  6.         if (vLocal == null) {
  7.             vLocal = newLocalData(group);
  8.             mData.set(vLocal);
  9.         } else {
  10.             resetLocalData(vLocal, group);
  11.         }
  12.     }

  13.     @Override
  14.     public boolean supportQuery() {
  15.         return false;
  16.     }

  17.     @Override
  18.     public void setCriteria(String criteria) {
  19.     }

  20.     protected T getLocalData() {
  21.         return mData.get();
  22.     }

  23.     protected void removeLocalData() {
  24.         mData.remove();
  25.     }

  26.     protected abstract void resetLocalData(T data, String group);

  27.     protected abstract T newLocalData(String group);

  28. }
复制代码
大体结构就是这样,其余就是写实现类了,就再赘述,想详细了解的直接看代码吧

用法

设置DataProviderX配置信息

配置信息中可以设置IDataReader、IGroupResolver、IRowFilter、Criteria

示例代码:
  1. // 使用默认配置:IDataReader使用ExcelReader,IGroupResolver使用DefaultGroupResolver
  2. DPXConfig config = DPXConfig.buildDefault("data.xls");
  3. DataProviderX.setConfig(config);

  4. // 也可以自己创建
  5. IDataReader reader = new DBReader("jdbc:sqlite:data.db", "org.sqlite.JDBC");
  6. IGroupResolver groupResovler = new DefaultGroupResolver();
  7. DPXConfig config = new DPXConfig(reader, groupResovler);
  8. DataProviderX.setConfig(config);
复制代码
设置测试方法注解

示例代码:

@Test(dataProvider = DataProviderX.NAME, dataProviderClass = DataProviderX.class)
DataProviderX提供了4个DataProvider方法,均可通过常量引用,分别是

常量        说明
DataProviderX.NAME        返回Object[][]的DataProvider
DataProviderX.NAME_PARALLEL        返回Object[][]的DataProvider,启用并发
DataProviderX.NAME_LAZY        返回Iterator的DataProvider
DataProviderX.NAME_LAZY_PARALLEL        返回Iterator的DataProvider,启用并发
设置数据分组名

在使用DefaultGroupResolver时,可以在测试方法上添加 DataGroup 注解来声明该方法的分组名,否
则将使用 类名_方法名 作为分组名;若使用其他的 IGroupResolver,请按照其规则设置数据分组名

支持的参数类型

DataProviderX 默认支持以下类型的参数:

带有 Key 注解的基本数据类型或java.lang.String、java.util.Date
带有 Mapper 注解的任意数据类型
IRow 类型
CustomRecord 类型
可以通过添加 IArgumentResolver 的实现类来让 DataProviderX 支持更多类型的参数,步骤如下:

创建一个类,实现 IArgumentResolver 接口,定义支持的参数格式和解析过程
调用 DataProviderX.RESOLVERS.addResolver() 方法添加刚才的类
示例代码:
  1. public class ArrayResolver implements IArgumentResolver {

  2.     @Override
  3.     public boolean support(Parameter param) {
  4.         return param.getType() == String[].class;
  5.     }

  6.     @Override
  7.     public Object resolve(Parameter param, IRow row) {
  8.         int len = row.getColumnNumbers();
  9.         String[] arr = new String[len];
  10.         for (int i = 0; i < len; ++i) {
  11.             arr[i] = row.getString(i);
  12.         }
  13.         return arr;
  14.     }

  15. }

  16. // 在使用DataProviderX前调用以下代码即可添加对String[]类型参数的支持
  17. DataProviderX.RESOLVERS.addResolver(new ArrayResolver());
复制代码
数据过滤

过滤数据有两种方式:

在 DPXConfig 中设置全局过滤器
在测试方法上添加 Filter 注解声明过滤器
注:测试方法上声明的过滤属性将会覆盖全局过滤属性

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏
回复

使用道具 举报

本版积分规则

关闭

站长推荐上一条 /1 下一条

小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

GMT+8, 2024-11-15 15:44 , Processed in 0.180939 second(s), 22 queries .

Powered by Discuz! X3.2

© 2001-2024 Comsenz Inc.

快速回复 返回顶部 返回列表