开发者测试
代码分析:
以典型的微服务为例,分Controller,Service,Dao(Mybatis Mapper),适配层,每一层都有各自的特点:
Controller:代表的是Restful API的出入口,Request为入数据,Response为出数据。看护好输入输出可以有效的看护好API层的能力。常规的自动化测试也在这个领域耕耘较多,如各种API Test的工具,包括PostMan等。在开发和测试团队分离的团队里面,测试主要聚焦此类的自动化测试。
Service层:该层的API,定位于基于业务模型的封装,为Controller提供能力业务模型的源自能力或者组合能力。为
Dao层:数据库操作的原子能力。与真实数据库交互验证,对环境依赖比较重。
适配层:与其他微服务组件交互的能力封装。供Service层消费。
常见问题
1、没有开发者测试,最典型:开发写完代码,交给测试同学测试。至于测试体系的构建,那是测试的事情。
2、迫于指标压力,业务代码和单元测试代码是两拨人写的,测试数据有效性较差,常见于完成任务。
3、理解开发者测试的重要性,奈何工具方法选择不当,导致在开发者测试花费的经历,不小于业务代码的开发维护工作维护工作量
经验分享
分享几条经验原则:
何为初心:为什么要做开发者测试?试想:在开发代码阶段,假定自定开发的业务代码没有测试同学来给自己测试,那么自己应该怎么保证整理,一两个需求可以手工测试,但日积月累数月,甚至数年。体力有限,想必也不可能完全充分验证,当然年轻力壮着除外。那么如何保障质量?
何得始终:
1、测试代码架构也需要设计,如何设计适合自己业务特征的单元测试框架。
2、选择合适的测试库,测试方法。每个人分享的看法经验都是个人的经历总结,都不具备普适性,不是公式,可以套用完事。
比如:“单元测试重点测试本类,本方法。被调用方法的测试由被调用方法的单元测试保证”,说的也没问题,但是是不是适合你?也不一定,自己琢磨
如何选择:
不说废话了,干正事
一、Controller层测试:
1、如果你的项目是Spring系列的,建议SpringRunner体系的单元测试。
优势:依赖注入,灵活的Mockbean体系,只需要mock掉该API业务中涉及的适配层调用第三方的API的打桩即可,做最少的桩,测试最深的代码路径。
劣势:需要启动Spring工程,速度略慢
二、Service层测试:
1、测试框架选型
选择1、SpringRunner
依然可以使用SpringRunner体系的单元测试。优劣势参考Controler层测试的分析。
选择2、Mockito、PowerMock
优势:运行速度快,测试灵活
劣势:不可控因素较多,尤其碰到有一些框架做了jar包隔离,有些类方法就不能用了,会出现NoClassDefine的错误。
PowerMock相比于Mockito,其支持Mock静态方法,私有方法,私有变量。看似强大,但建议,对于静态,私有方法,私有变量能不Mock就不Mock,尽可能还原业务本质。
2、有效的Mock和验证手段
2.1、参数校验器
可以验证mock对象的doSomething方法被调用的时候,参数的值是不是期望值
1
2
3
4ArgumentCaptor<Person> argument = ArgumentCaptor.forClass(Person.class); verify(mock).doSomething(argument.capture()); assertEquals("zhaosi", argument.getValue().getName());
2.2、函数调用次数
1
2
3
4
5
6verify(mockList, times(1)).size(); //调用1次 verify(mockList, atLeastOnce()).size(); // 大于1次调用 verify(mockList, atMost(2)).size(); // 最多2次调用 verify(mockList, atLeast(1)).size(); // 最少1次调用 verify(mockList, never()).clear(); // 从来没调用过
三、Dao(Mybatis Mapper)
使用H2内存数据库做单元测试。
1、bean的配置文件
创建test-spring-mybatis.xml
配置文件,后面讲会加载该配置文件
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
43
44
45
46
47<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <aop:aspectj-autoproxy expose-proxy="true" proxy-target-class="true"/> <!-- 主要定义java里面mapper接口路径,如果存在多个路径,则“;”分割 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.xxx.domain.*.teamdbrepository;com.xxx.domain.*.repository" /> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> </bean> <!-- 主要定义mapper.xml的文件路径,存在多个路径,则多条记录即可 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="helloDatasource"/> <property name="mapperLocations"> <array> <value>classpath:sqlmap/other/*.xml</value> <value>classpath:sqlmap/team/*.xml</value> </array> </property> </bean> <bean id="helloDatasource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="org.h2.Driver" /> <!-- 配置为h2以mysql模式在文件中运行,方便调试使用 --> <!-- <property name="url" value="jdbc:h2:file:D://hellodb;MODE=MYSQL;DB_CLOSE_DELAY=-1" />--> <!-- 配置为h2以mysql模式在内存中运行 --> <property name="url" value="jdbc:h2:mem:hellodb;MODE=MYSQL;DB_CLOSE_DELAY=-1" /> </bean> <!-- 初始化数据库的建表脚本 --> <jdbc:initialize-database data-source="helloDatasource" ignore-failures="DROPS"> <jdbc:script location="classpath*:hellodb*.sql" /> </jdbc:initialize-database> <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="helloDatasource"/> </bean> </beans>
2、加载test-spring-mybatis.xml
配置
1
2
3
4
5
6
7
8
9
10
11
12public class MyBatisTest { protected static ApplicationContext context; public static ApplicationContext getContext() { if (context == null) { context = new ClassPathXmlApplicationContext("classpath:META-INF/spring/test-spring-mybatis.xml"); } return context; } }
3、Mapper单元测试实例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public class TeamServiceScopeMapperTest { PersonMapper personMapper = MyBatisTest.getContext().getBean(PersonMapper.class); @Test public void testServiceScopeMapper() { PersonMapper personMapper = new PersonMapper(); Person person = new Person(); person.setId(1); personMapper.insert(person); Person person = personMapper.selectByPrimaryKey(1); Assert.assertNotNull(person); Assert.assertEquals(person.getId, "1"); } }
4、Mysql和H2的兼容性
虽然H2支持以MYSQL模式运行,但是在DDL以及一些深度Mybatis的函数上是不能支持的。差异主要如下:
注释
H2不支持表级Comment。
索引
MySql要求表级索引名称必须唯一,H2要求库级别索引必须唯一。也就是如果业务数据表设计的时候,Index在多个表中有重复,那在H2会出现Index重复错误
各种时间类函数
比如UTC_TIMESTAMP()函数,如果Mapper中有此类函数,在H2中会失败。可以在DDL中定义函数别名,在执行mapper中对应函数时,代理到java代码实现MySql 函数的支持。做如下两部:
1、在DDL分两部分,如下
1
2
3
4
5
6
7
8
9
10-- 函数映射部分,下例 CREATE ALIAS IF NOT EXISTS UTC_TIMESTAMP FOR "com.togo.mybatis.H2DateFunction.utcTimestamp"; -- 建表SQL部分, 下例 DROP TABLE IF EXISTS `t_person`; CREATE TABLE `t_person` ( `id` bigint(20) NOT NULL , `name` varchar(64) NOT NULL );
2、在com.togo.mybatis.H2DateFunction
中定义utcTimestamp
函数,如
1
2
3
4public static LocalDateTime utcTimestamp() { return ZonedDateTime.now(ZoneId.of("UTC")).toLocalDateTime(); }
引申:
1、数据驱动测试
待补充
2、Testng测试套
待补充
最后
以上就是轻松小笼包最近收集整理的关于开发者测试(未完待续)开发者测试的全部内容,更多相关开发者测试(未完待续)开发者测试内容请搜索靠谱客的其他文章。
发表评论 取消回复