-
单元测试的定义
-
什么是单元测试
单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。对于单元测试中单元的含义,一般来说,要根据实际情况去判定其具体含义,如C语言中单元指一个函数,Java里单元指一个类,图形化的软件中可以指一个窗口或一个菜单等。总的来说,单元就是人为规定的最小的被测功能模块。单元测试是在软件开发过程中要进行的最低级别的测试活动,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。在现行服务端代码中,单元测试定义为特指对某个类的某个方法的测试
-
单元测试不是集成测试
按照开发阶段划分,软件测试可分为单元测试、集成测试、系统测试和验收测试。
- 单元测试:用于验证编码单元的正确性,以确保每个模块能正常工作。
- 集成测试:对已测试过的模块进行组装,进行集成测试,目的在于检验与软件设计相关的程序结构问题。
- 系统测试:检验软件产品能否与系统的其他部分(比如硬件、数据库及操作人员)协调工作。
- 验收测试:检验软件产品质量的最后一道工序。主要突出用户的作用,同时软件开发人员也应有一定程度的参与。验收测试可分为Alpha测试与Beta测试,Alpha测试由用户在开发环境下完成,Beta测试由用户在用户环境下完成。
下图能很好的说明各测试阶段的侧重点及关联关系,
单元测试其实对应的就是我们的编码阶段,所以单元测试就应该是由程序员来编写的。
单元测试只是测试一个方法单元,它的粒度应该要足够小,它不是测试一整个操作流程,整个流程的测试应该属于集成测试或者以上的范畴之内了。
-
-
单元测试的原则
-
可靠性
所谓可靠性是指单元测试本身是正确的,即该失败的时候失败,该成功的时候成功。不该因为所在环境不同以及重复执行对单元测试的结果有任何的影响。只有保证单元测试的可靠性,才能让开发人员信任该单元测试
- 依据实际情况合理地删除或修改单元测试
如果确定是测试缺陷,而不是产品缺陷(被测试代码缺陷)时,需要立刻修改相关单元测试代码;如果被测试的产品代码的语义或者API变更导致测试失败,这时是需要修改测试,使用新的语义;如果看到测试名含义不清或者单元测试的可维护性差就应该在保证单元测试基本功能前提下修改测试名称或者重构测试;如果同一个功能多个单元测试,请删除重复测试。 - 避免在单元测试代码中包含逻辑
包含逻辑的测试是指测试代码中包含switch、if/else、for/while等控制流语句。这样的测试可读性差,代码脆弱,测试代码的复杂度高,容易包含缺陷,测试结果不容易重现 - 每个单元测试只测试一个关注点
所谓的一个关注点就是指一个工作单元的一个最终结果:一个返回值、系统状态的一个改变、对第三方对象的一个调用。测试多个关注点一方面不利于测试命名,另一方面很多单元测试框架中,一个失败断言就会抛出一个特殊类型的异常,后面代码不会继续执行,这样不利于收集测试失败原因。
- 依据实际情况合理地删除或修改单元测试
-
可读性
- 单元测试的命名标准
合理地命名测试,主要目的是为了使后来的开发者从为了理解测试而阅读代码的负担中解脱出来。测试名应该包含三部分:被测试方法名、测试场景(即测试使用的条件)、预期行为(即被测试方法的最终结果)。MethodName_WhenXXX_ShouldSuccessOrFailed() - 单元测试中的变量命名规范
单元测试除了主要的测试功能之外,它还为API提供某种形式的文档。通过合理命名变量,帮助阅读测试的人可以尽快理解你要验证什么(从而更加理解产品代码中想要实现什么功能) - 断言和操作分离
- 单元测试的命名标准
-
可维护性
-
只测试公共契约,避免测试私有或者受保护的方法
私有方法可以看做是系统内部契约,这个内部契约是动态,在系统重构时可能会被随时修改,因此针对这些内部契约的单元测试也很可能会失败。而内部契约最终都会被一个公共契约(公共方法、整体功能)所调用,也就是说任何私有方法通常都是一个更大的工作单元的一部分。 -
去除重复代码
-
实施测试隔离
测试隔离是指每个测试都只生活在自己的小世界中,它与其他测试之间没有任何依赖关系 -
避免对不同关注点多次断言,尽量使用参数化测试或者对每个关注点设计单独的测试用例
-
-
-
单元测试的前提条件
传统业务代码中,模块之间代码只有纵向切分,即使我们为了阅读方便,对于业务做了分层,但是在真实执行时作为一个整体来看还是过于庞大,为了能让单元测试关注的点足够明确和精炼,需要排除所有非当前测试单元的依赖, 包括当前测试单元的下层代码与第三方服务。如何做到这一点,需要针对现有代码进行重构以提高可测试性。
如何做到这一点,我们要先理清楚一个概念 blog.csdn.net/ivan820819/… 利用这个原理,我们可以实现一个DI,将被测单元与他所有依赖项隔离开。前提是代码本身已经做到了方法接口化。(方法接口化也是代码重构中的一个重要的点,不在这里讲述) -
SonarQube覆盖率的计算
-
什么是圈复杂度
圈复杂度(Cyclomatic Complexity)是一种代码复杂度的衡量标准,由 Thomas McCabe 于 1976年定义。它可以用来衡量一个模块判定结构的复杂程度,数量上表现为独立现行路径条数,也可理解为覆盖所有的可能情况最少使用的测试用例数。圈复杂度大说明程序代码的判断逻辑复杂,可能质量低且难于测试和维护。程序的可能错误和高的圈复杂度有着很大关系。
圈复杂度主要与分支语句(if、else、,switch 等)的个数成正相关。可以在图1中看到常用到的几种语句的控制流图(表示程序执行流程的有向图)。当一段代码中含有较多的分支语句,其逻辑复杂程度就会增加。在计算圈复杂度时,可以通过程序控制流图方便的计算出来。
-
降低圈复杂度的意义
1.增加代码的可阅读性(降低圈复杂度本身是代码重构中的一个要点,这里先不详细讲)
2.方便单元测试
-
圈复杂度对单元覆盖率的影响
我们在SonarQube上查看覆盖率的时候会有一个百分比,点击进入后会展示如下一个文件,左边的颜色区块中,绿色表示已经覆盖到 ,而红色表示目前并未覆盖。可能单元测试已经跑过某一个方法,但是方法内仍旧有许多的未覆盖代码,这就跟刚刚说的圈复杂度相关,一个圈复杂度过高的方法往往需要更多的测试用例来覆盖它的所有返回分支(单测原则中强调到私有方法不单独做单测,那么他的覆盖率将会以调用他的公有方法计算,圈复杂度同理)。所以组织自己代码的时候应该遵循
1.单一的方法实现的功能尽量专一,且逻辑清晰
2.多个方法中重复的部分尽量提炼成函数,避免写大量的测试代码来覆盖相同的代码
3.代码中无用的分支尽量精简,避免后期写测试用例时要花很多时间维护测试用例
-
-
实践案例
-
分析待测单元的依赖
-
重构代码对未封装的依赖进行封装
-
编写单测用例
-
-
VisualStudio 查看本地单测覆盖率方法
-
先卸载掉项目中非单元测试的其他测试项目(避免影响覆盖计算结果),因为集成测试大概率是无法重复执行的。
-
工具栏 → 测试 → 分析代码覆盖率 → 所有测试
可以看到我的测试方法中仅测试了其中一个方法,所以这个方法覆盖率是100%, 其他方法没有测试,那么覆盖率是0%, 测试代码本身就是执行所用所以是100%,其他一些公共代码执行过构造函数所以会有一部分覆盖
除开不可测的封装第三方接口的代码,保证项目中其他文件的覆盖率即可
-
推荐资料 《The Art of Unit Testing》