简介: TDD(Test Driven Development)是一种强调测试先行的开发方式,通过编写单元测试用例,有效保障存量复杂系统在开发、重构上的质量。本文通过分析现有测试方法面临的问题,分享如何使用GTest框架进行单元测试,以及在单元测试中的一些实践心得。

一 业务背景

高德在线导航服务作为有很强业务特性和多年历史积累的存量系统,不可避免的存在大量的不合理代码,而业务演进对系统性能、算法、底层架构等不断提出更高要求,存量的各种业务代码和算法、架构快速演进的诉求存在严重冲突,如何有效保障质量地进行快速重构式演进,成为业务发展面临的首要工程难题。

二 现有质量保障方法问题与分析

1 现有测试方法的问题

常规方法是对新老服务批量进行请求比较diff,这种方式简单有效,是我们一直在用的方法,但存在以下问题:

  • 无效diff问题:以公交规划引擎为例,依赖步导引擎、搜索、公交突发事件、路况等多个下游服务,获取结果的差异导致很多无效diff。

  • 运行时间较长:case量较多时运行时间较长,在10分钟级别。由于这一步成本较高,一般开发人员跑diff的频率不会太高,无法进行"每次一小步"的测试。

  • 排查困难:当发现diff后进行排查非常困难,因为是整个请求级别的diff,中间步骤可能都存在问题。

2 业界主流方法实践

ThoughtWorks、Google等公司使用TDD方式进行敏捷开发,通过编写单元测试用例保障开发、重构的质量,目前已经成为主流最佳实践。

三 单元测试介绍

1 什么是单元测试?

单元测试是对一个模块、一个函数或者一个类进行正确性检验的测试工作。

测试的粒度更小更轻量,运行时间在秒级,特别适合渐进式重构中的"每次一小步"的质量保障。

由于单元测试用例针对的是一个函数、类更细粒度的目标,所以当某个用例不通过时,可以快速锁定问题点。

2 单元测试框架

常见单元测试框架有 xUnit 系列,多种语言都有对应实现,如CppUnit、JUnit、NUnit...

GTest是Google开发的单元测试框架,此框架具有一些高级功能,如death test, mock等。

我们选择的是GTest框架。

3 单元测试、重构、TDD与敏捷

TDD(Test Driven Development)是强调测试先行的开发方式,这种方式的好处在于编写任何函数、修改任何代码时可以通过编写一个单元测试用例代码来表达要实现的代码功能,一个测试用例本身就是一个代码表达的需求。而积累起来的测试用例可以有效保障开发及后续重构演进的质量。

重构和TDD是敏捷方法的核心构成要素,脱离了TDD的敏捷是危险的,没有用例保障的重构一旦启动,就像一匹脱缰的野马。而单元测试和TDD则是缚住野马的缰绳。

四 公交服务单元测试实践

1 GTest框架集成

Git库地址:https://github.com/google/googletest

GTest框架集成非常简单,把googletest库加入到工程中, 增加链接 libgtest 即可:

![image.png](https://ucc.alicdn.com/pic/developer-ecology/a3218ea2dd67405fadf68a8749bc6cf9.png)

通过如下代码即可驱动用例执行:

int RCUnitTest::Excute()
{int argc = 2;char* argv[] = {const_cast<char*>(""), const_cast<char*>("--gtest_output=\"xml:./testAll.xml\"")};::testing::InitGoogleTest(&argc, argv);return RUN_ALL_TESTS();

开关控制:为避免影响到正式版本, 可以考虑通过编译控制,也可以增加一个配置项开关。

我们在使用时是在入口处通过一个配置项控制是否触发单元测试用例,编译时默认只链接入口文件,需要运行单元测试时添加上单元测试用例文件进行链接运行。

2 测试代码编写

通过实现一个Test类的派生类,然后使用TEST_F宏添加测试函数即可,如下示例:

class DateTimeUtilTest : public ::testing::Test
{
protected:virtual void SetUp()
{}virtual void TearDown()
{}
};TEST_F(DateTimeUtilTest, TestAddSeconds_leap)
{//闰年测试 2020-02-28tm tt;tt.tm_year = (2020 - 1900);tt.tm_mon = 1;tt.tm_mday = 28;tt.tm_hour = 23;tt.tm_min = 59;tt.tm_sec = 50;DateTimeUtil::AddSeconds(tt, 30);EXPECT_TRUE(tt.tm_sec == 20);EXPECT_TRUE(tt.tm_min == 0);EXPECT_TRUE(tt.tm_hour == 0);EXPECT_TRUE(tt.tm_mday == 29);EXPECT_TRUE(tt.tm_mon == 1);//非闰年测试 2019-02-28tm tt1;tt1.tm_year = (2019 - 1900);tt1.tm_mon = 1;tt1.tm_mday = 28;tt1.tm_hour = 23;tt1.tm_min = 59;tt1.tm_sec = 50;DateTimeUtil::AddSeconds(tt1, 30);EXPECT_TRUE(tt1.tm_sec == 20);EXPECT_TRUE(tt1.tm_min == 0);EXPECT_TRUE(tt1.tm_hour == 0);EXPECT_TRUE(tt1.tm_mday == 1);EXPECT_TRUE(tt1.tm_mon == 2);
};

测试用例执行效果:

![image.png](https://ucc.alicdn.com/pic/developer-ecology/cf0756e358224b93a9caee2890ae38ef.png)

目前公交引擎已经积累了23个模块测试用例,基本覆盖了寻站、寻路、ETA、票价、风险停运等核心功能,持续积累中。通过单元测试保障,每个版本开发活动中都在进行渐进式重构活动,能够有效保障质量,提测迭代次数和线上新增代码引入问题数量持续较低。

![image.png](https://ucc.alicdn.com/pic/developer-ecology/1518ac393cf94e56ad0c0983966ef199.png)

3 问题与难点

数据依赖问题

在线导航引擎是对数据重度依赖的业务,多组数据结构之间互相关联,字段繁多,很难脱离数据构建有效的单元测试。通过mock方式构造假数据成本很高。而数据变化将导致用例不能通过。

我的实践:

能够简单构造假数据的通过构造假数据来搞定。

对于很难构建假数据的情况,直接使用真实数据即可。数据变化可能导致这部分用例不通过,没有关系,只需要保障在每次重构前把相关的用例调通即可,这样仍可以确保重构过程的质量。即:不需要做到用例随时随地都能运行通过,而是保证重构前后都可以通过。

4 常见错误认知

对于没有真正实践过单元测试和TDD开发方式的同学来说,有一些认知上的常见误区,比如:

开发时间都不够, 哪有时间编写单元测试?

我的理解:

  • 首先TDD的开发方式强调的是测试先行,编写测试代码是在前面的,这个过程等于是理解需求的过程。即想清楚你要实现的是什么功能?这个测试代码是理清需求的产物, 如此而已,不存在更多时间成本。

  • TDD开发方式属于典型的一次投入,持续受益的事情,用例积累越多,越容易在早期发现问题,重构有了质量保障,代码越来越整洁清晰,开发同学们再也不用哀叹历史代码。

历史代码那么多,怎么补单元测试?

那就从添加第一个用例开始。我的做法是对应本次修改涉及到的代码添加用例,逐步积累。

添加用例的过程是理解现有代码的过程,对于存量的历史代码,各种硬性编码侵入,各种耦合,全局变量或长生命周期大对象,通过编写单元测试用例能够有效理清函数真正的输入输出,也为重构增加了有效保障。

五 存量复杂系统代码渐进式重构

对于我们一线码农,每天大部分时间都在和代码打交道,如果你维护的代码结构合理、易读易扩展,那么恭喜你!但大部分情况我们面对的是存在各种历史"积淀"的存量工程,各种牵一发而动全身,这种情况下小改动还可以靠多花时间,认真仔细来搞定,但想要做一些大的系统升级就难了。

而对于巨型业务系统来说,重写在成本和质量控制方面显得更不现实。那么设置几个大的节点,通过渐进式重构逐渐优化,变量变为质变,是综合来看最优的方式。

而单元测试和TDD,则是渐进式重构有效开展的必选方法。

原文链接

本文为阿里云原创内容,未经允许不得转载。

复杂系统如何保障代码质量?让测试先行相关推荐

  1. github 检查代码质量_07. 改善 GitHub 项目代码质量:测试

    改善 GitHub 项目代码质量:测试 TDD 虽然接触的TDD时间不算短,然而真正在实践TDD上的时候少之又少.除去怎么教人TDD,就是与人结对编程时的switch,或许是受限于当前的开发流程. 偶 ...

  2. 基于Web的质量和测试度量指标

    直观了解软件质量和测试的完整性 VectorCAST/Analytics可提供便于用户理解的web仪表盘视图来显示软件代码质量和测试完整性指标,让用户能够掌握单个代码库的趋势,或对比多个代码库的度量指 ...

  3. 代码质量管控的四个阶段

    本文讨论的代码质量指的是代码本身的质量,包括复杂度.重复率.代码风格等要素.代码是团队的共同财产,代码质量是团队技术水平和管理水平的直接体现. 代码质量下降通常会自成因果,导致恶性循环: 破窗效应:在 ...

  4. 代码扫描 | 把控代码质量的利器

    本文作者:潘金赤 -- CODING 产品总监 腾讯云研发平台负责人,十年研发能效建设经验 CODING 代码扫描产品负责人 有位小伙子在办公大楼门口抽烟,一位路人经过他的身边对他说:"你知 ...

  5. 光流法测试代码_高效的企业测试-工作流和代码质量(4/6)

    光流法测试代码 本文的这一部分将讨论在开发过程中拥有有效工作流程的影响,以及适当的测试代码质量如何使我们能够创建可维护的测试,尤其是对于复杂项目. 开发工作流程和管道 编程是一项流程活动,我们开发人员 ...

  6. 高效的企业测试-工作流和代码质量(4/6)

    本文的这一部分将讨论在开发过程中拥有有效工作流程的影响,以及适当的测试代码质量如何使我们能够创建可维护的测试,尤其是对于复杂项目. 开发工作流程和管道 编程是一项流程活动,我们开发人员应该对保持工作流 ...

  7. 代码质量与安全 | 一文了解高级驾驶辅助系统(ADAS)及其开发中需要遵循的标准

    高级驾驶辅助系统(ADAS)有助于提高车内每个人的安全性,帮助他们安全抵达目的地.这项技术功能非常重要,因为大多数的严重车祸都是人为错误造成的. 本篇文章将讨论什么是高级驾驶辅助系统(ADAS),提供 ...

  8. 【测试沉思录】1. 如何保障需求质量(上):你应该知道的

    欢迎订阅我的新专栏<现代命令行工具指南>,精讲目前最流行的开源命令行工具,大大提升你的工作效率. 每个测试人员都知道保障需求质量非常重要,那到底为什么这么重要?又如何来保障需求质量呢?如果 ...

  9. 【测试沉思录】2. 如何保障需求质量(下):你应该做到的

    欢迎订阅我的新专栏<现代命令行工具指南>,精讲目前最流行的开源命令行工具,大大提升你的工作效率. 上一篇文章我们介绍了保障需求质量需要知道的一些基本内容,接下来,我们看保障需求质量的具体措 ...

最新文章

  1. git放弃修改放弃增加文件
  2. pear Auth的使用
  3. bootstrap-wysiwyg中JS控件富文本的用法
  4. Elasticsearch、Elasticsearch-head、Kibana 安全设置账号密码验证登陆
  5. Go知识点:slice、map、func、struct、method、interface、channel、goroutine
  6. leetcode剑指 Offer 42. 连续子数组的最大和(动态规划)
  7. centos 安装pm2
  8. 算法工程师思维导图—数据结构与算法
  9. glassfish服务器默认的网页所在的位置
  10. java初始化数据报_java – 如何在Docker中初始化数据库后启动flyway
  11. sql去重常用的基本方法
  12. TCP/IP Model: Layers Protocol | What is TCP IP Stack?
  13. smbclient用法
  14. android图片资源加载和异常
  15. 重庆大学计算机学院考研,2021年重庆大学计算机学院考研指南
  16. DevOps前沿技术培训课程大纲
  17. 今日算法笔试练习【5】(08-06)(历年笔试题)
  18. 运维工作发展的几个阶段
  19. Java零基础必看学习教程,Java开发环境配置详解
  20. 如何利用python给女友制造惊喜(微信每日weather report)

热门文章

  1. android 选择答题功能,Android APP编写简单答题器
  2. Linux下软件自启
  3. Android 自定义控件基础:MeasureSpec
  4. unity中球体的碰撞检测公式
  5. backgroundImage加载图片报403解决方法
  6. 两个世界2城堡防御攻略
  7. KNN算法简单应用之预测队伍胜利
  8. 股票模拟交易日志(三)
  9. 小班安全使用计算机教案,安全小班教案范文(通用5篇)
  10. nodejs 邮件服务(网易126邮箱为例)