3

淘系用户平台技术团队单元测试建设-51CTO.COM

 2 years ago
source link: https://developer.51cto.com/article/708764.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

作者 |  问元

为什么需要单元测试

纵观优秀的开源工程,完备的单元测试总是必须的条件。通过这些单元测试,我们可以充分了解代码中相关类和方法的作用和核心逻辑,熟悉各种场景的运行情况。同时也因为有了单元测试,开源作者在接受各种feature的代码提交时才有稳定安全的保障。

其实单元测试的重要性所有开发同学应该都了然于胸,同样TDD(测试驱动开发)也不是一个新的概念,但是真当我们落地实践时,又总会找出各种各样的理由来劝服自己下次一定好好写单元测试,这一次先放过自己。这些理由无外乎,开发周期太紧了; 测试同学能保证功能正确性;写单元测试代码量比业务代码还大; 又不是不能跑。所以虽然我们总是在追逐工程师文化,却又时不时放纵在放弃工程师底蕴的路上。

单元测试是工程交付前质量保障的第一环,也无疑是软件工程质量保障的重要基石,有效的单元测试能够提前发现90%以上的代码Bug问题,同时也能防止代码的腐化,在工程重构演进时起到至关重要的作用。

怎么写单元测试

好的单元测试的几个要点

摘自阿里巴巴开发规约:

  • 单元测试必须遵守AIR原则,单元测试必须具备Automatic(自动化),Independent(独立性),Repeatable(可重复)性;
  • 单元测试应该是全自动执行的,并且非交互式的。测试用例通常是被定期执行的,执行过程必须完全自动化才有意义。输出结果需要人工检查的测试不是一个好的单元测试;
  • 单元测试要保证测试粒度足够小。单元测试测试粒度足够小,有助于精确定位问题。单测粒度至多是类级别,一般是方法级别;
  • 单元测试要遵守BCDE原则,Border,边界值测试,包括循环边界、特殊取值、特殊时间点、数据顺序等;Correct,正确的输入,并得到预期的结果;Design,与设计文档相结合,来编写单元测试;Error,强制错误信息输入(如:非法数据、异常流程、非业务允许输入等),并得到预期的结果;
  • 核心业务、核心应用、核心模块的增量代码要确保单元测试通过;

单元测试编码范式

这里主要以Mockito单元测试框架为模版

1.Mock :通过when().thenReturn/thenAnswer/thenThrow 或者doReturn().when()等mock方式将依赖类方法进行模拟,模拟服务依赖或者中间结果。

2.DO : 调用被测试类方法,执行测试链路。

3.Verify : 校验执行结果正确性,通过Assert校验数据结果准确,通过Verify校验链路执行准确,通过expected=Exception.class校验异常链路。

public class Test {
    // 0. 依赖类
    @Mock
    DependencyClass dependencyClass;
    // 0. 待测试类
    @InjectMocks
    TestClass testClass;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testMethod() {
        // 1. Mock, 依赖方法,构造中间层数据
        when(dependencyClass.someMehod(any())).thenReturn(mockData());
        // 2. Do, 调用被测试类
        Result result = testClass.testMehod();
        // 3. Verify, 校验结果数据
        Assert.assertEquals("some expected result string", result.getModel());
    }
}

当然写单元测试用例虽然套路比较模版化,但是我们也要充分利用单元测试框架(Junit/Mockito/PowerMock/Spock),掌握其中的一些技巧,才能写出快准狠的单元测试用例,这也是研发同学必须要掌握的基本功。

单元测试编码提效

IDEA上有很多单元测试插件,能够半自动化生成单元测试类文件,这里重点推荐TestMe插件。TestMe插件可以智能分析被测试类的依赖类,结合Mockito+Junit等单元测试框架,生成Mock/InjectMocks依赖关系,自动生成单元测试类。

5964ad76616c94a063b852994ae01d29c8646c.png

假设业务代码如下:

@Component
public class DefaultMemberManager implements MemberManager {
    @Autowired
    private MemberDAO memberDAO;
    @Autowired
    private CacheManager cacheManager;

    @Override
    public Date queryActivationTime(long userId) {
        Date activationTime = cacheManager.getActivationTime(userId);
        if (activationTime == null) {
            MemberDO memberDO = memberDAO.queryByUserId(userId);
            if (memberDO != null) {
                cacheManager.saveActivationTime(userId, memberDO.getActiveTime());
                activationTime = memberDO.getActiveTime();
            }
        }
        return activationTime;
    }
}

则通过TestMe快捷键COMMOND+N, 可以极速自动生成如下的单元测试类

public class DefaultMemberManagerTest {
    @Mock
    MemberDAO memberDAO;
    @Mock
    CacheManager cacheManager;
    @InjectMocks
    DefaultMemberManager defaultMemberManager;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testQueryActivationTime() throws Exception {
        when(memberDAO.queryByUserId(anyLong())).thenReturn(null);
        when(cacheManager.getActivationTime(anyLong())).thenReturn(
            new GregorianCalendar(2022, Calendar.MARCH, 5, 23, 2).getTime());
        Date result = defaultMemberManager.queryActivationTime(0L);
        Assert.assertEquals(new GregorianCalendar(2022, Calendar.MARCH, 5, 23, 2).getTime(), result);
    }
}

团队单元测试建设

覆盖率概念

覆盖率是类JaCoCo插件通过javaagent挂载的方式,在单元测试命令运行时执行代码覆盖率检测,计算单元测试执行过程中所覆盖的代码比例来生成覆盖率。常见的覆盖率指标,又可进一步细分为语句覆盖率,条件覆盖率,分支覆盖率,路径覆盖率等。这里我们当前更为关注语句覆盖率和分支覆盖率,尤其是增量代码的覆盖率,更能体现变更代码的单元测试覆盖情况。

如何进行单元测试

这里我们借助于阿里研发平台Aone的测试实验室功能,Aone实验室支持测试任务插件的编排组合,通过独立的测试资源执行测试任务。所以我们将代码拉取插件,单元测试插件和覆盖率计算插件进行编排配置,形成最终的执行流:拉取代码;执行单元测试命令;单元测试结果解析;计算覆盖率。最终完成整个工程的单元测试覆盖率计算。

08f7f2c20462dd92e0c170b97c5776c23310f7.png

单元测试覆盖率结果示例如下:

c4161e721d659c06bd489972c9aa7a76bf8857.png

什么时候触发单元测试

单元测试任务主要通过持续交付流水线pipeline来集成,当前几个主要触发策略如下

  • 代码提交时,保证单元测试执行及时性
  • 代码审核时,保证代码审核通过的代码分支符合单元测试标准
  • 发布流程中,保证最终集成发布的所有分支代码符合单元测试标准

单元测试覆盖率卡点

用户平台技术团队单元测试规范如下:

  • 单元测试用例通过率为100%
  • 单元测试增量代码行覆盖率为85%
  • 代码规范扫描增量问题总数为0个

单元测试覆盖率报表

为了更好的衡量单元测试的覆盖率情况,我们采用报表的形式统计每个应用,每个团队的代码单元测试覆盖率。

b731f07891863ab1018732b3c439162b725ab1.png

当前团队内各应用(除边缘应用外)的单元测试增量代码覆盖率在2022年已经全部达到85%标准,最新平均增量代码行覆盖率达到88%,整体全量代码覆盖率平均提高20%。诚然单元测试覆盖率的提高不是最终的目的,覆盖率高不能完全代表工程质量高,但是一个没有单元测试或者单元测试覆盖率低的工程,其代码质量和稳定性必然不高。同时团队内研发同学对单元测试也有了新的认识,自测和提测质量显著提升,全年未发生由于代码质量产生的线上故障,有效提升了工程质量和服务稳定性。

后续规划,持续优化单元测试质量,提升分支覆盖率,优化边界异常覆盖;关注单元测试编码效率的提升,优化测试用例和测试数据分离;关注核心链路单元测试覆盖率;熟练将TDD思维运用到业务开发过程中。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK