测试框架设计思路
测试框架设计
价值
- 简化自动化测试技术
- 规范领域测试模型
- 数据驱动与API相结合
- 自动生成用例:分析的结构化数据,生成代码格式的用例非常复杂,直接保存为数据格式更好的
- 与云平台对接:数据保存到了数据库的表结构里,数据的传输转换也更适合使用数据
测试框架设计思路
测试框架的核心要素xUnit
- Test Runner
- Test Case
- Test Fixtures
- Test Suites
- Test Execution
- Test Result Formatter
- Assertions
xUnit框架体系
- Java:Junit4、TestNG、JUnit5
- Python:UnitTest、PyTest
几乎所有的语言都是xUnit实现
测试框架的用途
- 单元测试
- Web自动化测试Selenium
- Appium自动化测试Appium
- 接口自动化测试Requests、Rest-Assured
承载特定领域的测试用例管理
领域建模与抽象封装
- 业务领域建模
- 自动化领域建模
- 抽象为资源对象、操作方法、状态切换
- Page Object模式
用例表达方式
- TDD:xUnit
- junit/testng+po+param,适合测试开发
- DDT:数据驱动测试(测试服务化)
- 非测试开发人员(业务测试、产品,研发、甲方),平台化支持(与其他框架对接,自动生成、框架切换、平台调度)
- ATDD:验收测试驱动开发,代表作RobotFramework
- BDD:行为驱动开发,代表作Cucumber
- API:使用领域特定api描述测试,代表作Requests、RestAssured
Api方法已经成为测试框架的主流使用方法
参数化与数据驱动
- 参数化:将用例的关键数据变成参数以实现批量数据驱动
- 关键数据变为外部传入参数
- 执行时使用数据替换参数
- 数据驱动测试:使用管理良好的外部数据表达测试并驱动测试执行
- 代码与数据结构
- 关键测试数据来源于外部数据源
数据驱动常见应用
- 测试数据的数据驱动
- 测试步骤的数据驱动
- 全局配置的数据驱动
数据驱动
- 数据来源:csv、yaml、xml、db、excel、json
- 读取数据源返回数组:
- 基于schema:
List<Class>
- 纯数据:
List<HashMap> List[Dict]
- 基于schema:
- 利用参数化进行数据与变量的对应
数据格式的选择
优点 | 缺点 | |
---|---|---|
Excel | 生成数据方便 | 二进制文件不利于版本管理 |
CSV | 可使用Excel编辑 | 表达多层级多类型数据有困难 |
XML | 格式完备 | 冗长复杂 |
JSON | 格式完备,可读性一般 | 不能编写注释,格式死板 |
YAML | 格式完备,可读性好 |
测试数据的数据驱动
接口测试,我们关注的是输入输出的结果是否符合我们的预期,在调用接口的时候,接口是固定的,但是参数是不一致的,下面我们看个例子
1 | package com.api.test.apiobject.accounting; |
这是一个传入日期判断是否是工作日的接口,根据传入不同的数据,对应不同的结果,我们结合Junit5参数化的功能,只要设定好输入和输出的数据,在传入接口测试时,进行断言,我们就可以快速的测试接口的多种情况。
测试步骤的数据驱动
核心技术概念
- 模板替换:使数据源中的数据动态化
- 变量引用:可以导出变量并在后续步骤中引用
- 自定义扩展:支持自定义的编程逻辑
- xUnit测试封装:用例、套件、执行、断言、装置等等
具体实现
建立自动化领域模型
使用yaml文件管理模型数据(用例、配置)
- 测试步骤:基本自动化领域模型,业务模型的设计,以下几种方式
- 方式一:通用方法及带有参数,这种方式,会使模型的内容繁多冗长
1
2
3
4
5
6
7
8
9name: 企业微信新增通讯录成员
deascription: 企业微信新增通讯录成员
steps:
- method: click
params: [1, 2]
- method: click
params:
by: id
value: search - 方式二:简化格式
1
2
3
4
5
6name: 企业微信新增通讯录成员
deascription: 企业微信新增通讯录成员
steps:
- by: id
value: search
action: click - 方式三:进一步简化,如果by只是单个简单的字符串
1
2
3
4
5
6
7
8
9
10name: 企业微信新增通讯录成员
deascription: 企业微信新增通讯录成员
steps:
- id: click
action: click
- id: search
action: sendKeys
text: "xiaohei"
- id: search
sendKeys: "xiaohei" - 方式四:根据上一步,再次简化
1
2
3
4name: 企业微信新增通讯录成员
deascription: 企业微信新增通讯录成员
steps:
- click: {id: search}
- 方式一:通用方法及带有参数,这种方式,会使模型的内容繁多冗长
- 测试步骤:基本自动化领域模型,业务模型的设计,以下几种方式
编写读取yaml文件的代码
使用jackson读取yaml文件
maven pom.xml引入依赖
1
2
3
4
5
6
7
8
9
10
11<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.11.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.11.0</version>
</dependency>定义与yaml内容对应实体AutoModel.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25package com.test.framework.pojo;
import java.util.HashMap;
import java.util.List;
/**
* UI页面的基本建模
*
* @author jingLv
* @date 2020/12/07
*/
public class AutoModel {
/**
* 用例名称
*/
public String name = "";
/**
* 用例描述
*/
public String description = "";
/**
* 用例步骤
*/
public List<HashMap<String, Object>> steps;
}使用jackson读取方法封装
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18/**
* 加载(yaml文件中建模的数据)
*
* @param path yaml文件路径
*/
public AutoModel load(String path) {
ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
AutoModel autoModel = null;
try {
autoModel = mapper.readValue(
BasePage.class.getResource(path),
AutoModel.class
);
} catch (IOException e) {
e.printStackTrace();
}
return autoModel;
}测试:
yaml文件内容:
1
2
3
4
5
6
7name: 百度
description: 百度搜索
steps:
- action: get
url: https://www.baidu.com/
- click: {id: "su"}1
2
3
4
5
6
void load() throws JsonProcessingException {
AutoModel autoModel = basePage.load("/uiInfo/baiduUI.yaml");
ObjectMapper mapper = new ObjectMapper();
System.out.println(mapper.writeValueAsString(autoModel));
}执行结果:
新建PO的BasePage基类
改造Web的BasePage为WebBasePage和App的BasePage为AppBasePage,并都继承BasePage的基类
yaml文件生成PO模型
动态传参运行
动态传参,用例执行不依赖代码,使用Junit5提供的Launcher的方式进行命令调度传参
junit-platform-console-standalone的Jar包下载
下载jar包
1
wget https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone/1.7.0/junit-platform-console-standalone-1.7.0.jar
指定参数运行
1
2
3
4
51. 测试工程maven进行打包
mvn package -DskipTests/mvn package -Dmaven.test.skip=true
2.运行Launcher
java -jar junit-platform-console-standalone-1.7.0.jar -cp /Users/apple/JavaProject/test-framework/target/test-framework-1.0-SNAPSHOT.jar 参数目前使用不会,后续在补充
Lanncher编写代码执行测试用例
maven工程添加依赖:
1
2
3
4
5<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-launcher</artifactId>
<version>1.6.2</version>
</dependency>
编写执行测试代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43package com.test.framework;
import com.test.framework.testcase.WebTest;
import org.junit.platform.launcher.Launcher;
import org.junit.platform.launcher.LauncherDiscoveryRequest;
import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder;
import org.junit.platform.launcher.core.LauncherFactory;
import org.junit.platform.launcher.listeners.SummaryGeneratingListener;
import org.junit.platform.launcher.listeners.TestExecutionSummary;
import static org.junit.platform.engine.discovery.ClassNameFilter.includeClassNamePatterns;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass;
import static org.junit.platform.engine.discovery.DiscoverySelectors.selectPackage;
/**
* @author jingLv
* @date 2020/12/09
*/
public class RunTest {
public static void main(String[] args) {
LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request()
.selectors(
selectPackage("com.test.framework"),
// 执行的测试类
selectClass(WebTest.class)
)
.filters(
includeClassNamePatterns(".*")
)
.build();
Launcher launcher = LauncherFactory.create();
SummaryGeneratingListener listener = new SummaryGeneratingListener();
launcher.registerTestExecutionListeners(listener);
launcher.execute(request);
TestExecutionSummary summary = listener.getSummary();
System.out.println(summary.getTestsSucceededCount());
}
}
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Jing's Blog!