码出高效-Unit Test

1、单元测试的基本原则

宏观上, 单元测试要符合AIR 原则;微观上, 单元测试的代码层面要符合BCDE原则。AIR 即空气, 单元测试亦是如此。当业务代码在线上运行时, 可能感觉不到测试用例的存在和价值, 但在代码质量的保障上, 却是非常关键的。新增代码应该同步增加测试用例, 修改代码逻辑时也应该同步保证测试用例成功执行。AIR 原则具体包括:

  • A : Automatic (自动化)
  • I : Ind ependent (独立性)
  • R : Repeatable (可重复)

单元测试应该是全自动执行的。测试用例通常会被频繁地触发执行, 执行过程必须完全自动化才有意义。如果单元测试的输出结果需要人工介入检查,那么它一定是不合格的。单元测试中不允许使用System.out 来进行人工验证,而必须使用断言来验证。为了保证单元测试稳定可靠且便于维护, 需要保证其独立性。用例之间不允许互相调用, 也不允许出现执行次序的先后依赖。如下警示代码所示, testMethod2 需要调用testMe thod I 。在执行testM ethod2 时会重复执行验证testM ethod I ,导致运行效率降低。更严重的是, testMethodI的验证失败会影响testMethod2 的执行。

@Test
public void testMethodl() {
...
}

@Test
public void testMethod2() {
testMethodl ();
...
}

在主流测试框架中, JU nit 的用例执行顺序是无序的,而TestNG 支持测试用例的顺序执行(默认测试类内部各测试用例是按字典序升序执行的,也可以通过泪也或注解priority 的方式来配置执行顺序)。单元测试是可以重复执行的, 不能受到外界环境的影响。比如, 单元测试通常会被放到持续集成中,每次有代码提交时单元测试都会被触发执行。如果单测对外部环境( 网络、服务、中间件等)有依赖,贝IJ 很容易导致持续集成机制的不可用。编写单元测试时要保证测试粒度足够小, 这样有助于精确定位问题,单元测试用例默认是方法级别的。单测不负责检查跨类或者跨系统的交互逻辑,那是集成测试需要覆盖的范围。编写单元测试用例时,为了保证被测模块的交付质量,需要符合BCDE 原则。

  • B: Border , 边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等。
  • C: Correct , 正确的输入, 并得到预期的结果。
  • D: Des i gn , 与设计文档相结合,来编写单元测试。
  • E : Error , 单元测试的目标是证明程序有错,而不是程序无错。为了发现代码中潜在的错误, 我们需要在编 写测试用例时有一些强制的错误输入(如非法数据、异常流程、非业务允许输入等)来得到预期的错误结果。

由于单元测试只是系统集成测试前的小模块测试,有些因素往往是不具备的,因
此需要进行M ock , 例如,

1、功能因素。比如被测试方法内部调用的功能不可用。
2、时间因素。比如双十一还没有到来3 与此时间相关的功能点。
3、环境因素。政策环境,如支付宝政策类新功能,多端环境, 如PC 、手机等。
4、数据因素。线下数据样本过小,难以覆盖各种线上真实场景。
5、其他因素。为了简化测试编写, 开发者也可以将一些复杂的依赖采用Mock方式实现。

最简单的Mock 方式是硬编码,更为优雅的方式是使用配置文件,最佳的方式是使用相应的Mock 框架, 例如JMockit 、EasyMock 、JMock 等。Mock 的本质是让我们写出更加稳定的单元测试, 隔离上述因素对单元测试的影响, 使结果变得可预测,做到真正的“单元”测试。

2、单元测试覆盖率

单元测试是种白盒测试, 测试者依据程序的内部结构来实现测试代码。单测覆盖率是指业务代码被单测测试的比例和程度,它是衡量单元测试好坏的一个很重要的指标,各类覆盖率指标从粗到细、从弱到强排歹lj如下。

I. 粗粒度的覆盖

粗粒度的覆盖包括类覆盖和方法覆盖两种。类覆盖是指类中只要有方法或变量被测试用例调用或执行歪lj , 那么就说这个类被测试覆盖了。方法覆盖同理, 只要在测试用例执行过程中,某个方法被调用了,贝lj 无论执行了该方法中的多少行代码,都可以认为该方法被覆盖了。从实际测试场景来看, 无论是以类覆盖率还是方法覆盖率来衡
量测试覆盖范围,其粒度都太粗了。以阿里研发场景为例, 大多数开发工程师都能做到类覆盖率和方法覆盖率达到100% ,但这并不能说明测试用例已经写得很好,因为这个标准是远远不够的。

2 细粒度的覆盖

细粒度的覆盖包括以下几种。

( I )行覆盖( Line Coverage )

行覆盖也称为语句覆盖,用来度量可执行的语旬是否被执行到。行覆盖率的计算
公式的分子是执行到的语旬行数, 分母是总的可执行语句行数。示例代码如下,

public class CoverageSampleMethods {
public Boolean testMethod(int a , int b , int c) {
   boolean resul t = false ;
   if (a == 1 & & b == 2 || c = = 3) {
       result = true ;
   }
   return result ;
   }
}	

以上方法中有5 行可执行语旬和3 个人参,针对此方法编写测试用例如下

@Test
@DisplayName (” line coverage sample test ” )
void testL工neCoverageSample() {
   CoverageSampleMethods coverageSampleMethods = new CoverageSampleMethods();
   Assertions.assertTrue(coverageSampleMethods.testMethod( 1 , 2 , 0 ));
}

以上测试用例的行覆盖率是100% ,但是在执行过程中c==3 的条件判断根本没有被执行到, a!=I 并且c!=3 的情况难道不应该测试一下吗?由此可见,行覆盖的覆盖强度并不高,但由于容易计算,因此在主流的覆盖率工具中,它依然是一个十分常见的参考指标。

{ 2 )分支覆盖{ Branch Coverage )

分支覆盖也称为判定覆盖,用来度量程序中每一个判定分支是否都被执行到。分支覆盖率的计算公式中的分子是代码中被执行到的分支数,分母是代码中所有分支的总数。譬如前面例子中,( a == I && b == 2 II c = 3 ) 整个条件为一个判定, 测试数据应至少保证此判定为真和为假的情况都被覆盖到。分支覆盖容易与下面要说的条件判
定覆盖混淆,因此我们先介绍条件判定覆盖的定义,然后再对比介绍两者的区别。

( 3 )条件判定农盖( Condition Decision Coverage )
条件判定覆盖要求设计足够的测试用例,能够让判定中每个条件的所有可能情况至少被执行一次, 同时每个判定本身的所有可能结果也至少执行一次。例如(a 二l&&b 二2 11 c 二3 ) 这个判定中包含了3 种条件, ~D a== I 、b ==2 和c =3 。为了便于理解,下面我们仍使用行覆盖率中的tes tMethod 方法作为被测方法,测试用例如下,

@ParameterizedTest
@DisplayName ( ” Condition Decision coverage sample test result true)
@CsvSource({02 , 3,103 ” ,
})
void testConditionDecisionCoverageTrue (int a , int b , int c) {
     CoverageSampleMethods coverageSampleMethods = new CoverageSampleMethods() ;
     Assertions.assertTrue(coverageSampleMethods.testMethod(a, b, c)) ;
}

@DisplayName ( ” Condition Decisior coverage sample test result false)
void testCondi tionDecisionCoverageFalse () {
     CoverageSampleMethods coverageSampleMethods = new CoverageSampleMethods() ;
     Assertions.assertTrue ( coverageSa mple Methods.testMethod ( O , 01); 
}     

通过@ParameterizedTest , 我们可以定义个参数化测试,@CsvSource 注解使得我们可以通过定义一个String 数组来定义多次运行测试时的参数列表,而每一个String 值通过逗号分隔后的结果,就是每一次测试运行时的实际参数值。我们通过两个测试用例分别测试判定结果为true 和false 这两种情况, 第一个测试用例testConditionDecisionCoverageTrue 会运行两次, a、b 、c 这3 个参数的值分别为0 , 2 、3 和1 、0 、3;

第二个测试用例testConditionDecisionCoverageFalse 的3 个参数的值都为0。在被测方法testMethod 中,有个判定( a== 1 && b == 2 || c == 3 l 包含了三个条件( a== 1 、b == 2 、c== 3 ),判定的结果显而易见有两种(true、false ), 我们已经都覆盖到了。另外,我们设计的测试用例,也使得上述三个条件真和假的结果都取到了。因此,这个测试用例满足了条件判定覆盖。反过来再看下分支覆盖,分支覆盖只要求覆盖分支所有可能的结果, 可以看出它是条件判定覆盖的一个子集。

( 4 )条伴组合覆盖( Multiple Condition Coverage)

条件组合覆盖是指判定中所有条件的各种组合情况都出现至少一次。还是以
(a == 1 && b == 2 || c == 3)这个判定为例,我们在介绍条件判定覆盖时,忽略了如al 、b2 、c==3 等诸多情况。针对被测方法testMethod ,满足条件组合覆盖的一个测试用例如下,

@ParameterizedTest
@DisplayName ( ” Mult 工ple Condition Coverage sample test result true)
@CsvSource ( {1, 2, 3,1, 2, 0",
"1, 0, 3",
"0, 2, 3",
"0, 0, 3",
})
void testMultipleConditionCoverageSampleTrue(int a, int b , int c) {
    CoverageSampleMethods coverageSampleMethods = new CoverageSampleMethods();
    Assertions . assertTrue(coverageSampleMethods . testMethod(a , b, c)) ;
}

@ParameterizedTest
@DisplayName( "Multiple Condit 工on Coverage sample test result false " )
@CsvSource({
"1, 0, 0",
"0, 0, 0",
})
void testMultipleConditionCoverageSampleFalse(int a , int b , int c) {
    CoverageSampleMethods coverageSampleMethods = new CoverageSampleMethods();
    Assertions.assertFalse(coverageSampleMethods . testMethod(a , b , c)) ;    
}    

3、JUnit 单元测试框架

Java 语言的单元测试框架相对统一 , JUnit 和TestNG 几乎始终处于市场前两位。其中JUnit 以较长的发展历史和源源不断的功能演进, 得到了大多数用户的青睐,也是阿里内部目前使用最多的单元测试框架。
JUnit 项目的起源可以追溯到1997 年。两位参加“面向对象程序系统语言和应用大会”的极客开发者Kent Beck 和Erich Gamma , 在从瑞士苏黎世飞往美国亚特兰大的飞机上, 为了打发长途飞行的无聊时间,他们聊起了对当时Java 测试过程中缺乏成熟工具的无奈,然后决定起设计一款更好用的测试框架, 于是采用结对编程的方式在飞机上完成了JUnit 雏形,以及世界上第一个JUnit 单元测试用例。
经过20 余年的发展和几次重大版本的跃迁, JUnit 于2017 年9 月正式发布了5.0 稳定版本。JUnit5 对JDK8 及以上版本有了更好的支持(如增加了对lambda 表达式的支持),并且加入了更多的测试形式,如重复测试、参数化测试等。因此本书的测试用例会使用JUnit5 采编写,部分写法如果在JUnit4 中不兼窑,则会提前说明。

JUnit5.x 由以下三个主要模块组成。

  • JUnit Platform:用于在NM 上启动测试框架, 统一命令行、Gradle 和Maven等方式执行测试的入口。
  • JUnit Jupiter: 包含JUnit5.x 全新的编程模型和扩展机制。
  • JUnit Vintage: 用于在新的框架中兼容运行JUnit3.x 和JUnit4.x 的测试用例。

为了便于开发者将注意力放在测试编写上, 即不必关心测试的执行流程和结果展示, JUnit 提供了一些辅助测试的注解, 常用的测试注解说明如下表所示。
在这里插入图片描述
下面是个典型的JUnit 测试类结构:

在这里插入代码片

太累了,不写了。。。以后我有机会想起来的时候再写吧!!!
2021-2.16 21:37 萧炎 上海浦东新区 川沙

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值