理论部分
前言
单元测试,就是对某一段细粒度的Java代码的逻辑测试。代码块一般指一个Java 方法本身,所有外部依赖都需要mock掉,仅关注代码逻辑本身。
需要注意,单测的一个大前提就是需要清楚的知道自己要测试的程序块所预期的输入输出,然后根据这个预期和程序逻辑来书写case。
(这里需要注意的就是单测的预期结果 一定要针对需求/设计逻辑去写,而不是针对实现去写,否则单测将毫无意义,照着错误的实现设计出的case也很可能是错的)
覆盖类型
1、行覆盖 Statement Coverage
行覆盖(又叫语句覆盖)就是通过设计一定量的测试用例,保证被测试的方法每一行代码都会被执行一遍。
路径覆盖是最弱的覆盖方式。
实例:
public Integer fun3(Integer a, Integer b, Integer x) { if (a > 1 && b == 0) { x = x + a; } if (a == 2 || x > 1) { x += 1; } return x; }
本例仅需要一个case,即可实现行覆盖。test case 如下:
a |
b |
x |
预期结果 |
|
TC1 |
2 |
0 |
3 |
6 |
@Test public void testFun3StatementCoverage(){ Integer res = demoService.fun3(2,0,3); Assert.assertEquals(6,res.intValue()); }
这个用例就可以保证所有的行都被执行。
但是仅仅有这一个用例的话,对这个方法的测试就是非常脆弱的。
举个栗子,某RD接到了这个需求,理清了逻辑,写好单测之后开始写代码(或者写好代码之后开始写单测)。但是由于手抖,将第三行的 && 写成了 ||:
public Integer fun4(Integer a, Integer b, Integer x) { if (a > 1 || b == 0) { x += a; } if (a == 2 || x > 1) { x += 1; } return x; }
然后跑一下单测,发现很顺滑,一下就过了。
随后该RD很高兴的将代码发布到了线上,结果就发生了严重的生产故障,于是该RD就被开除了。
行覆盖是一个最基础的覆盖方式,但是也是最薄弱的,如果完全依赖行覆盖,那不小心就会被开除。
2、判定覆盖 / 分支覆盖 (Decision Coverage/Branch Coverage)
public Integer fun3(Integer a, Integer b, Integer x) { if (a > 1 && b == 0) { x = x + a; } if (a == 2 || x > 1) { x += 1; } return x; }
判定覆盖的含义就是代码里每一个判定都要走一次true,一次false。依然用上面的代码,想要实现判定覆盖,需要以下case
a |
b |
x |
预期结果 |
|
TC2 |
2 |
0 |
1 |
4 |
TC3 |
3 |
1 |
1 |
1 |
@Test public void testFun3DecisionCoverage(){ Integer res = demoService.fun3(2,0,1); Assert.assertEquals(4,res.intValue()); res = demoService.fun3(3,1,1); Assert.assertEquals(1,res.intValue()); }