《有用的单元测试》 读书笔记
程序员文章站
2022-06-01 16:43:22
...
有效的测试
第4章 断言
- 看见魔数的时候,或者
!=
、==
这样的判断的时候,要考虑是否足够抽象, - 一个测试应该只有一个失败的原因,过度assert
- 不要过度断言,任何一点小的改变都会使得测试失败
- 不要在断言中使用位运算
- 准备-执行-断言
- 内敛数据或者逻辑,不要轻易使用外部数据文件
- 不要使用魔数
- 冗长的setup,可以封装到其他的private方法中
第5章 可维护性
- 重复:语法重复和语义重复,使用参数化测试减少模版重复代码
- 参数化测试,不能精准定位到到底是哪一组测试数据出了问题,需要在断言的失败消息中表明当前测试数据
- 尽量避免条件逻辑,if、switch、while、for,
- 因为网络、io、时间戳、随机数、多线程等导致测试不确定性失败
- 使用Stream流代替File,因为File类是final的,不容易mock
- 避免硬编码绝对路径,使用相对路径,将所有测试文件放到classpath下面,
- 能不用物理文件就不用
- 使用
countdownlatch
代替Thread.sleep
获取线程正确的结束时间,确保线程执行完
第6章 可信赖
- 不要遗留注释掉的测试,要么重构,要么删除
- 避免永不失败的测试,尝试使测试失败,expected,注意try-catch导致测试永远不抛出异常
- 跟平台相关,可以使用
assumeTrue
, - 存在有条件的测试,可以将if换成断言
第7章 指南
- 避免静态初始化,导致无法实例一个对象
- 避免复杂的private方法,尝试短小的private方法,或者单独一个public方法或者一个类
- 避免final方法,final是为了覆盖,以及内联带来性能优势,但是final方法不易测试,
- 避免将可能会打桩的方法设置为static,比如产生随机数的方法
- 在方法中,慎用new产生新的实例,这样导致测试很难覆盖该对象
- 避免在构造函数中添加复杂的逻辑,确保构造函数中的代码,不会在单元测试中替换他们,可以将其抽取到子类容易覆盖的protected方法中
- 避免单例,也可以单例的
getinstance()
方法,返回一个接口,而不是类接口更容易伪造 - 降低代码重复时,多考虑使用组合,而不是继承
第9章 加速执行测试
- 通过maven的几个插件,能够查看测试执行的时间和每个单元测试执行的时间,sufire,找到最慢的
-
countdownlatch
替代sleep
- 注意测试基类和setup、teardown方法中是否有大量的代码,仅仅只是针对某个测试,而其他测试用不到
- 执行一个测试的顺序,
- 找到所有父类和本类的
@beforclass
方法,
- 找到所有父类和本类的
@before
方法 - 执行测试
- 追踪父类以及本类的
@after
方法 - 追踪父类以及本类的
@afterclass
方法
- 找到所有父类和本类的
- 当一个
@befor
中的方法只用被执行一次,可以考虑将其放到@beforclass
中 - 保持本地运行,使用mock避免网络、io等
- 最小化对数据库的访问
- 考虑关闭日志,测试失败了之后,再开启执行一次
- 如果cpu够用,可以考虑并行构建和并行测试
- 并行构建
mvn -T {线程数} clean install
mvn -T 1C clean install
- 并行测试
- 在suefire插件中配置并行测试
- 并行构建
junit
测试类
- 测试方法必须是public void修饰,没有任何参数,并使用@Test注解
- 测试方法的生命周期
- 实例化测试类的一个实例
- 调用父类以及当前类所有的setup
- 执行测试方法
- 调用父类以及当前类所有的teardown
- setup和teardown可以在本类中定义,也可以在父类中定义
- 多个setup和teardown都会被执行,但是不保证执行顺序
- @beforclass 和 @afterclass针对测试类不是测试方法,每个测试类只会被执行一次
断言
- 断言api org.junit.Assert
-
@Test(expected="异常")
替代try-catch - 如果想要检查异常的信息,要回滚到try-catch
- 使用try-catch要注意在try中最后调用fail(),防止不抛出异常测试通过
-
assertThat(someobj,matchthiscondition)
,matchthiscondition
是Hamcrest匹配器,并且可以自定义实现Hamcrest的Matcher
接口,
扩展junit
- class-level注解
@RunWith
选择运行器,比如参数化测试@Runwith(Parameterized.class)
,通常默认runner在最后 - 为单个测试方法配置超时
@Test(timeout=x)
- 为一个测试类中所有的方法配置超时,
@Rule
public MethodRule globalTimeout = new Timeout(20)
- 除了
@Test(expected=x)
,还以使用ExpectedException
为单个方法甚至是测试类中的所有方法设置异常
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void thispassed() {}
@Test
public void throwExpectedException() {
thrown.expected(Exception.class);
}
- 临时文件夹
@Rule
public TemporaryFolder folder = new TemporaryFolder();