前言
先吐个槽,参加过很多技术大会,也看过个很多技术类文章,发现大部分存在一个通病,即:都会提问题,提思路,但是都不会讲具体的落地方案,所以我写东西给自己定了一个目标,即:能够落地,尽量提供一个小而简单的 Demo 让感兴趣的同学能快速上手。好了,这里啰嗦两句,下面进入正题。
在上两篇中,我们先介绍了需求功能,然后讲解了大概的框架设计,今天这篇主要看用例管理功能怎么落地去实现。
走进Java接口测试之从0到1搭建数据驱动框架(需求篇)
走进Java接口测试之从0到1搭建数据驱动框架(设计篇)
开发环境
- SUN JDK1.8及以上
- Maven 3.5.4及以上
- IntelliJ IDEA 2018及以上
- windows/macOS
- Git 不限
- MySQL 5.7及以上
- Navicat Premium 11.2.7及以上 或 SQLyog 11.3及以上
新建Spring Boot项目
这里使用的 IDE 是 IntelliJIDEA2018
: 引包,配置 pom.xml:
1. <dependencies>
2. <!--MyBatis、数据库驱动、数据库连接池-->
3. <dependency>
4. <groupId>org.mybatis.spring.boot</groupId>
5. <artifactId>mybatis-spring-boot-starter</artifactId>
6. <version>2.1.1</version>
7. </dependency>
8.
9. <!--MySQL驱动-->
10. <dependency>
11. <groupId>mysql</groupId>
12. <artifactId>mysql-connector-java</artifactId>
13. <scope>runtime</scope>
14. </dependency>
15.
16. <dependency>
17. <groupId>org.projectlombok</groupId>
18. <artifactId>lombok</artifactId>
19. <optional>true</optional>
20. </dependency>
21.
22. <dependency>
23. <groupId>org.springframework.boot</groupId>
24. <artifactId>spring-boot-starter-test</artifactId>
25. <scope>test</scope>
26. <exclusions>
27. <exclusion>
28. <groupId>org.junit.vintage</groupId>
29. <artifactId>junit-vintage-engine</artifactId>
30. </exclusion>
31. </exclusions>
32. </dependency>
33.
34. <!--引入 testng 测试框架-->
35. <dependency>
36. <groupId>org.testng</groupId>
37. <artifactId>testng</artifactId>
38. <version>6.14.3</version>
39. <scope>compile</scope>
40. </dependency>
41.
42. </dependencies>
全部代码骨架结构
1. ├─logs
2. │ └─spring-boot-logback # 日志文件
3. │ all_api-test-logback.log # 所有日志
4. │ err_api-test-logback.log # 错误日志
5. ├─src
6. │ ├─main
7. │ │ ├─java
8. │ │ │ └─com
9. │ │ │ └─zuozewei
10. │ │ │ └─springbootdatadrivendemo
11. │ │ │ │ SpringbootDataDrivenDemoApplication.java # 启动类
12. │ │ │ │
13. │ │ │ ├─db
14. │ │ │ │ ├─auto # 存放MyBatis Generator生成器生成的数据层代码,可以随时删除再生成
15. │ │ │ │ │ ├─mapper # DAO 接口
16. │ │ │ │ │ └─model # Entity 实体
17. │ │ │ │ └─manual # 存放自定义的数据层代码,包括对MyBatis Generator自动生成代码的扩展
18. │ │ │ │ ├─mapper # DAO 接口
19. │ │ │ │ └─model # Entity 实体
20. │ │ │ ├─handler # 数据转换
21. │ │ │ └─service # 业务逻辑
22. │ │ │ └─impl # 实现类
23. │ │ │
24. │ │ └─resources
25. │ │ │ application.yml # 全局配置文件
26. │ │ │ generatorConfig.xml # Mybatis Generator 配置文件
27. │ │ │ logback-spring.xml # logback 配置文件
28. │ │ │ spy.properties # P6Spy 配置文件
29. │ │ │
30. │ │ ├─db
31. │ │ ├─mapper
32. │ │ │ └─com
33. │ │ │ └─zuozewei
34. │ │ │ └─springbootdatadrivendemo
35. │ │ │ └─db
36. │ │ │ ├─auto # 存放MyBatis Generator生成器生成的数据层代码,可以随时删除再生成
37. │ │ │ │ └─mapper # 数据库 Mapping 文件
38. │ │ │ │
39. │ │ │ └─manual # 存放自定义的数据层代码,包括对MyBatis Generator自动生成代码的扩展
40. │ │ │ └─mapper # 数据库 Mapping 文件
41. │ │ └─testng
42. │ │ │ APICollection-TestSuite.xml # 所用测试用例集
43. │ │ └─jdbcbapi
44. │ │ jdbcAPI-TestSuite.xml # 某API测试用例集
45. │ │
46. │ └─test
47. │ └─java
48. │ └─com
49. │ └─zuozewei
50. │ └─springbootdatadrivendemo
51. │ └─demo # 接口测试用例
52. ├─pom.xml
测试用例管理
MySQL数据库
创建测试用例表:
1. CREATE DATABASE /*!32312 IF NOT EXISTS*/`autotest` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_vietnamese_ci */;
2.
3. USE `autotest`;
4.
5. /*Table structure for table `api_testdata_demo` */
6.
7. DROP TABLE IF EXISTS `api_testdata_demo`;
8.
9. CREATE TABLE `api_testdata_demo` (
10. `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '测试ID',
11. `Protocol` enum('Http','RPC','jdbc') DEFAULT NULL COMMENT '协议',
12. `Category` enum('Webapi','db') DEFAULT NULL COMMENT '接口类别',
13. `Method` varchar(128) DEFAULT NULL COMMENT '接口名称',
14. `Parameters` varchar(1000) DEFAULT NULL COMMENT '参数',
15. `expected` varchar(128) DEFAULT NULL COMMENT '检查点',
16. `description` varchar(1000) DEFAULT NULL COMMENT '描述',
17. `isRun` enum('1','0') DEFAULT NULL COMMENT '运行状态,1:运行,0:未运行',
18. PRIMARY KEY (`id`)
19. ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
20.
21. /*Data for the table `api_testdata_demo` */
22.
23. insert into `api_testdata_demo`(`id`,`Protocol`,`Category`,`Method`,`Parameters`,`expected`,`description`,`isRun`) values (1,'jdbc','db','demo','latte','CNY 25.00','测试demo','1');
创建完成大概是这样: 这里的 SQL 主要决定了选取哪些测试用例进行测试:
1. SELECT * FROM autotest.api_testdata_demo
2. WHERE Protocol = 'jdbc'
3. AND Category = 'db'
4. AND Method = 'demo'
5. AND isRun = 1;
注意:SQL 取用例是非常灵活,可以根据自己的业务调整表结构。
持久层开发
这里使用 mybatis 直接使用原生的 SQL 查询测试用例的数据。
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
Mapper.xml
编写对应的 TestDataMapper.xml:
1. <?xml version="1.0" encoding="UTF-8" ?>
2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
3. <mapper namespace="com.zuozewei.springbootdatadrivendemo.db.manual.mapper.TestDataMapper" >
4.
5. <!-- 自定义SQL语句 -->
6. <select id="selectBysql" parameterType="String" resultType="java.util.LinkedHashMap">
7. ${value};
8. </select>
9.
10. </mapper>
注意:
- 这里是使用的是
${}
,而不是#{}
,这是因为如果我们使用#{}
的时候,MyBatis 会自动帮我们把我们的字段自动加上单引号',使用${}
的时候就不会; - 使用
java.util.LinkedHashMap
作为返回类型,可以保持结果集本来的字段顺序。
Dao接口
dao 层增加 TestDataMapper.java:
1. /**
2. * 描述:
3. * 自定义sql查询
4. *
5. * @author zuozewei
6. * @create 2019-11-21 21:18
7. */
8.
9. public interface TestDataMapper {
10.
11. // 自定义sql查询
12. List<LinkedHashMap<String, Object>> selectBysql(String sql);
13.
14. }
Service 的接口 TestDataService :
1. /**
2. * 描述: TestDataService
3. *
4. * @author zuozewei
5. * @create 2019-11-21 18:00
6. */
7.
8. public interface TestDataService {
9.
10. // 自定义查询
11. List<LinkedHashMap<String, Object>> selectBysql(String sql);
12.
13. }
实现 Service 的接口调用方法:
1. /**
2. * 描述: 参数化自定义查询实现类
3. *
4. * @author zuozewei
5. * @create 2019-11-21 16:04
6. */
7.
8.
9. @Service
10. public class TestDataServiceImpl implements TestDataService {
11.
12. @Resource
13. private TestDataMapper testDataMapper;
14.
15. @Override
16. public List<LinkedHashMap<String, Object>> selectBysql(String sql) {
17. return testDataMapper.selectBysql(sql);
18. }
19.
20. }
为了避免出现值 null 的列,不能被保存到 LinkedHashMap 对象 中,需要在 application.yml
的配置文件中 mybatis 如下配置:
1. mybatis:
2. configuration:
3. call-setters-on-nulls: true # 调用setter null,返回空也必须设置到bean中(直接执行sql专用)
脚本参数化
脚本参数化主要使用 TestNG 的 @DataProvider
& Testng.xml
首先我们在resource下创建一个 testng 配置文件:
1. <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd" >
2.
3. <suite name="jdbc 测试" verbose="1" preserve-order="true" >
4.
5. <test name="测试demo" preserve-order="true">
6. <parameter name="sql"
7. value="SELECT * FROM autotest.api_testdata_demo
8. WHERE Protocol = 'jdbc'
9. AND Category = 'db'
10. AND Method = 'demo'
11. AND isRun = 1;"/>
12.
13. <classes>
14. <class name="com.zuozewei.springbootdatadrivendemo.demo.TestMapperService"/>
15. </classes>
16. </test>
17.
18. <!--<listeners>-->
19. <!--<listener class-name="com.zuozewei.springbootdatadrivendemo.demo.listener.ExtentTestNGIReporterListener"/>-->
20. <!--</listeners>-->
2. </suite>
解释一下配置文件:
- SQL的话,这里的SQL主要决定了选取哪些测试用例进行测试。
- 一个标签,就代表一组测试,可以写多个标签。
- “listener”是为了最后能够生成一个报告。
编写脚本代码 TestMapperService.java:
1. @SpringBootTest
2. @Slf4j
3. public class TestMapperService extends AbstractTestNGSpringContextTests {
4.
5. private String sql; // SQL参数
6.
7. @Autowired
8. private TestDataService testDataService;
9.
10. @Parameters({"sql"})
11. @BeforeClass
12. public void beforeClass(String sql) {
13. this.sql = sql;
14. }
15.
16. /**
17. * XML中的SQL决定了执行什么用例, 执行多少条用例, SQL的搜索结果为需要测试的测试用例
18. */
19.
20. @DataProvider(name = "testData")
21. private Object[][] getData() {
22. List<LinkedHashMap<String, Object>> results = testDataService.selectBysql(sql);
23. Object[][] objects = new Object[results.size()][];
24. for (int i = 0; i < results.size(); i++) {
25. objects[i] = new Object[]{results.get(i)};
26. }
27. return objects;
28.
29. }
30.
31. @Test(dataProvider = "testData",description = "测试demo")
32. public void testSelect(Map<String, String> data) throws InterruptedException {
33. // to do something...
34. }
35.
36. }
注意:
- SpringBoot 中使用 TestNg 必须加上 @SpringBootTest,并且继承
AbstractTestNGSpringContextTests
,如果不继承AbstractTestNGSpringContextTests,会导致 @Autowired 不能加载 Bean。 - @Parameters({"sql"}):从 xml 配置文件中获取 SQL语句;
- @DataProvider 的数据来源是 MySQL;
- @Test:测试逻辑地方。
工程结构
最后,用例管理的工程结构大概是以下的样子:
小结
在今天这篇文章中,主要基于 SpringBoot 框架的能力,和大家分享了实现一个用例管理的过程。在实现过程中,你最需要关注的几部分内容是:
- 使用目前的主流 SpringBoot 2.2.0 作为项目的主体框架;
- 使用 Maven 作为构建项目,方便管理依赖的 JAR 包;
- 使用 MySQL 集中式管理测试用例,结构化数据;
- 使用 TestNG 作为测试框架,强大的参数化功能,方便执行测试脚本;
- MySQL 数据库管理测试用例,SQL 参数化驱动用例运行,实现测试脚本和数据的解耦;
至此,我们要实现接口用例集中式管理功能,也算是完成了。