我们以Flutter为例,来一起讨论如何写有效的单元测试。
使用测试框架
Flutter官方提供的测试框架:
·flutter_test
· integration_test
统一的编码约定
不论是AAA(Arrange-Act-Assert)还是GWT(Given-When-Then),统一的编码约定帮助保证测试代码的可读性、可维护性。
使用测试替身
测试替身帮助我们隔离被测试代码,加速执行速度,保证测试代码是可信赖的。
Dummy:一种什么也不做的实现方式。接口中的每个方法什么也不做,如果方法有返回值,返回的值尽量接近null或者0。
Stub:Dummy的一种,Stub的函数并不返回null或0,而是返回能推动函数沿预定路径被测试的值。
Spy:Stub的一种,它返回测试所需的特定值,推动系统沿着我们期望的路径前行。然而,Spy能记住对它所做的事,并允许测试询问。
Mock:Spy的一种,它返回测试所需的特定值,推动系统沿着我们期望的路径前行,而且还会记住对它所做的事。不过,Mock还知道我们的预期,基于这些预期,判断测试是否通过;换而言之,Mock中写明了测试断言。
Fake:Fake是一种模拟器,它实现基础业务规则,这样测试就能要求该Fake按需要的路径执行。
一个测试应当只检查一件事
明确测试意图,一旦出错可以精准定位问题。
一个测试只有一个模拟对象
避免过多模拟对象,一个测试用例的校验内容尽量简单。
避免冗余测试
冗余测试会提高维护成本。
避免条件逻辑
条件逻辑会让你的单元测试更难以维护,出问题不容易排查,不够精准。
单测需要确定性
避免脆弱测试,Mock不确定的依赖:时间、随机数、并发性、基础设施、现存数据、持久化、网络等等。
测试快速执行
避免sleep等操作,导致测试执行缓慢。
避免过度指定
对于过度指定的讨论,其核心问题就是要我们判断哪些是单元测试应该覆盖的,哪些是应该留给其他测试手段的。如果一个场景,单元测试覆盖之后,导致经常单测失败,需要不断更新维护,那就可以考虑不做单元测试覆盖。
像素完美是一个典型的、经常拿出来讨论的例子,Flutter的Golden Test就是一个golden master testing的例子;《有效的单元测试》中关于像素完美的讨论:
像素完美:顾名思义,是一种特定于图形和图像生成的测试坏味道。它混杂了魔法数字和基本断言,使得测试极难阅读也极其脆弱。
这种测试几乎无法阅读,因为即使测试在语义上是处于高层概念的,却仍然会针对硬编码的底层细节例如像素坐标和颜色来进行断言。指定坐标上的像素是黑还是白,与两个图形是否相连或堆叠的概念是有区别的。
这种测试极其脆弱,因为即使很小的和不相关的输入变化——是否是另一个图像,或图形对象的渲染方式——都足以影响输出、打破测试,谁让你非要精确地检查像素坐标和颜色呢。同样的问题在采用golden master技术时也会遇到,其做法是事先将图像录制下来,并手工检查其正确性,以后再进行测试时就将渲染出的图像与之进行比对。
这些可不是我们愿意去维护的测试。我们不希望带着这种脆弱的精确度去编写测试,而是使用模糊匹配和智能算法来代替繁琐的数值比较。
对于特定场景,Golden Test是一个非常有效的手段,但需要非常谨慎的评估;慎用Golden Test!
不要写永不失败的测试,不要写没有校验的测试
单测需要对明确的逻辑校验,永不失败的测试或者没有校验的测试是不可信赖的。
测试不要名不副实
避免测试的描述与测试内容不符;测试结果必须精准;测试该失败的时候一定要失败!
测试私有或者受保护的方法
解决思路:
1、将方法变成公共方法;
2、将方法抽取到新类;
3、将方法变成静态方法;
4、将方法成为测试可见方法;
避免强制的测试顺序
依赖测试顺序导致测试可靠性变得脆弱,未来维护成本变高。
清理测试环境
统一的单测命名、变量命名
统一的单测命名可以提高可读性、可维护性。
使用有意义的断言
断言的错误信息要有意义,出现问题能够明确错误的原因。
把单元测试视为“一等公民”
测试用例应该被视为“一等公民”:同样需要代码评审,同样需要代码质量检查,确保单元测试的有效性;
单元测试代码评审的过程,也是团队同学互相学习的过程,沉淀最佳实践的过程。
加速执行速度。
日常对单测执行时间进行监控,对测试进行性能分析,优化执行时间过长的测试用例。
本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理