持续集成的三板斧

CodeCipherPro
• 阅读 4249

相信各位读者肯定体验过持续集成(CI)吧。持续集成通常用来确保当前代码库的质量,反映软件开发的进度。有了持续集成后,程序员们提交代码也会变得更加小心谨慎。应该没有人乐意让组里其他同事不停地见到自己的分支上CI失败的邮件吧(笑)?

一个最简单的CI应该就是单纯地在模拟的生产环境下执行单元测试,然后报告单测的结果。
稍微复杂点的CI可能还会对代码复杂度等多项指标进行分析。其实,作为编码规范的度量尺、代码质量的把关者、项目健康的测量仪,CI可以做的事情还有很多,足以帮助减少code review(如果有的话)的负担呢,并时时警醒你不要写出敷衍的代码。

本文谈谈在持续集成中你可以用到的三板斧:linter -> unit test -> code smell reporter

  • linter可以对代码做静态分析,找到有问题的地方并报告。

  • unit test是持续集成的根基所在。

  • code smell reporter可以衡量项目代码的健康度,督促程序员们关注代码质量,让整个程序健康成长。

幸运的是,关于这三个领域,大部分都已经有了现成的开源工具。你所需的,也许是些微小的工作:把它们集成到你们项目的CI工作流中。也许只是往现有的CI服务中添加额外的几个新流程。假如你的同事不愿意接受这样的CI工作流,你也可以选择独自在自己本地运行它们。通过Git钩子,你可以在每次commit的时候把它们运行一遍。(其他版本控制系统估计也有相似的机制)

在本文的末尾,我会附上一些主流编程语言在这三个领域里常用的工具,以便读者入手。

linter

所谓的代码静态分析,就是使用linter来分析代码文本,找出一些浅显的问题。这些“显而易见”的问题包括未定义变量、未定义函数、未使用的变量、未使用的参数、不可达的分支等等。这些事情也可以放到code review的时候人工去做。然而交由专门的工具来完成,可以大大地节省人工review的时间。这样一来,code review的时候可以专注于接口命名、代码功能设计、逻辑缺陷、安全漏洞、可读性等机器目前无法胜任的工作。另外,人会失手,而机器不会。以前我看过一本类似于访谈录的书,里面有句留下了深刻印象的话,大意是“如果没有自动化的linter保证代码风格一致,就不要在这方面苛求他人,因为人人都有犯错的可能”。

编译器/解释器可以帮忙完成一部分的代码分析,比如gcc -Wall -Wextra -Weffc++ -Woverloaded-virtual。也有用来完成更高阶的代码分析的专业linter。

许多的linter专门用来评判代码风格是否符合要求,如[pep8](https://pypi.python.org/pypi/pep8/)。老实说,我并不认为代码风格是什么值得重视的事情。它之所以会被屡次强调,只是因为每个人都能轻易地对此评头论脚(相对于更隐晦的逻辑缺陷等问题而言)。我见过一些符合pep8规范的代码,它们对边界条件的处理还不如对换行的处理严谨。与其关注于某段代码是否合乎规格,不如关注于其他更实际的问题。当然了,实在写得太过于混乱的代码除外。

切回正题。大多数的linter关注于找出代码中的瑕疵的。虽然你可能会觉得,一般的程序员不会犯下诸如变量未初始化、编写不可达的分支这样的问题,但是人就是容易犯错,偶尔的typo也是在所难免的。所以,还是选择一个最聪明而又不饶舌的linter,让它督促你养成良好的编码习惯,矫正代码中的细微错漏吧。

某些时候,linter可能会作出“假阳性”的报告。举个例子,有些语言的回调函数要求提供某些入参,然而这些入参不一定在回调函数里用到。所以是否通过了linter可以不作为持续集成成功与否的前提,而是独立作为一份报告列在持续集成的结果里。当然如果你的linter支持复杂的配置,也可以在代码或配置文件里面调整linter的行为,比如提示linter在某段代码内关闭某个检测,例如@SuppressWarnings(...)。这个就见仁见智了。

unit test

单元测试的重要性深入人心,应该没有必要再啰嗦一遍。毕竟单元测试是持续集成的根基所在,连单元测试都没有就勿论持续集成了。

单元测试的编写风格大致分成两个流派,TDD和BDD:

# TDD风格
class TestA < MiniTest::Test
  include A
  
  def test_arithmetic_one_equal_one
    assert_equal 1, 1
  end
end
# BDD风格
RSpec.describe A, 'test' do
  context 'arithmetic' do # context是可选的
    it 'one equal one' do
      expect(1).to.eq 1
    end
  end
end

就我个人的看法,TDD和BDD没有什么不同。同样的测试用例,以TDD还是BDD的风格编写在击键次数上没有明显的区别,可读性也差不多。BDD对TDD的一个优势是,它的层级较为灵活。由于TDD往往是用一个测试类来对应一个实现类(用一个测试模块来对应一个实现类是可行的,不过到了这一步你该想想是不是实现类过于臃肿了),导致默认的层级只有两级:类和方法。如果想把若干测试用例独立分组,就要使用test_group_name_test_a这样的长方法名。而BDD由于describecontext可以反复嵌套,在层级这方面没有限制。不过在实际应用中,多于三个层级的需求很罕见,所以BDD这个优势不明显。一般来说,对于测试框架通常是随大流。因为主流的测试框架会有更多的集成支持。如果没有主流意见,选自己熟悉和所需击键次数少的。

说到集成支持,测试框架所不能或缺的是对覆盖率的统计。如果测试框架自身不提供该功能,就要靠额外的工具提供支持了。单元测试会给人一种幻觉,就是以为有了单元测试就说明对接口行为的变更就一定是稳定的。过低的测试覆盖率比没有测试更加可怕。

软件开发中常常出现这样的场景:
开始项目:老大:“同志们,一定不能忘记写测试!” 其他程序员:“好的老大!”
项目又要延期了:其他程序员:“写单测占去了1/3甚至1/2的时间。现在工期紧,我就不写测试了,将来再补上吧。” 老大:“好吧。”
于是乎测试覆盖率渐渐往下掉。明明是新功能加得最多的时候,写的测试却变少了,到后来都算不上有单元测试了,各种变更捎来的BUG也慢慢潜伏起来。
面汤上就飘着两三片牛肉,好意思叫牛肉面?单元测试行覆盖率不到70%,好意思叫有单元测试?

总之,是否有单元测试并不重要,重要的是覆盖率可以达到多少。这一点在持续集成时一定要统计出来,作为一个指标提倡编写新功能的程序员也要编写同覆盖率的单元测试。

由于单元测试内各组件往往会被mock掉,要想真正保证各组件能协同合作,程序员们还需要编写集成测试。不过集成测试可能涉及较为复杂的环境配置,所以持续集成中一般不包含集成测试。

code smell reporter

我们来看看三板斧中最后一击——code smell reporter,专门用于报告代码中的“异味”。所谓的“代码异味”,指的是软件中一些可能导致深层次问题的迹象,如

  • 过长的方法

  • 巨型的类

  • 太多的入参

  • 不依赖成员变量的成员方法

  • 某个方法过于依赖另一个类

  • 冗余代码

  • 过高的循环复杂度
    等等。

code smell reporter工作方式跟linter很像,也是解析代码文本,生成抽象语法树,然后处理之。事实上,有些linter也提供了报告代码异味的功能。那这两者间有何不同?在我看来,code smell reporter会更加关注于类/函数层面上的缺陷,而且有些code smell reporter会始终如一地反馈相关数据,以便于对代码质量进行追踪。

正如软件工程中的其他指标一样,code smell也有其局限性。什么算是“过长”?什么算是“太多”?什么算是“巨型”?这些指标即使在同一个项目中,也会因功能的不同而不同。可惜的是,目前还没看到可以按不同的文件指定不同配置的code smell reporter。你只能按整个项目去配置它,而这会迫使用户忽略某些文件中的报告。随便提一下,许多code smell reporter默认的阈值太低,总要调高一下。

不管怎么样,虽然code smell reporter有其不足之处,但是总比完全不重视强。可以像linter一样,在持续集成报告中专门做一个页面来展示code smell reporter的输出结果。当然了,更为关键的是以此激励编码的时候重视代码质量,消除code smell reporter的抱怨。

附录

主流语言的部分开源工具(每种最多列一个,未在此列出不代表不推荐):

编程语言 linter unit test code smell reporter
C gcc -Wall -Wextra Check -
C++ g++ -Wall -Wextra -Weffc++ GTest -
C# Gendarme NUnit SonarQube
Java javac -Xlint Junit SonarQube
Javascript jshint jasmine plato
Objective-C clang -Wall -Wextra -Wmost -Weverything OCUnit oclint
PHP PHP_CodeSniffer PHPUnit PHP_CodeSniffer
Python flake8 pytest radon
Ruby ruby-lint minitest reek
点赞
收藏
评论区
推荐文章
Tommy744 Tommy744
4年前
针对开发人员的21个Jenkins替代方案
当谈到CI/CD工具时,我们都会提到Jenkins。它是构建和测试项目的超级有效工具,从而使持续不断的轻松集成成为可能。但是,Jenkins并不是唯一的CI/CD工具。我们还有其他很多选择!1.GitLab它是一个开源的Web系统,可用于将持续集成,持续部署应用到你的项目中,而无需任何第三方应用程序。它提供了友好的用户界面以及分布式版本控制
Stella981 Stella981
3年前
CentOS 7 部署Gitlab+Jenkins持续集成(CI)环境
持续集成概述及运行流程:持续集成概述:持续集成(Continuousintegration)持续集成是指开发者在代码的开发过程中,可以频繁的将代码部署集成到主干,并进行自动化测试 开发→代码编译→测试持续交付:持续交付指的是在持续集成的环境基础之上,将代码部署到预生产环境持续部署:在持续交付的基础上,把部署到
Stella981 Stella981
3年前
CODING DevOps 微服务项目实战系列第二课来啦!
近年来,工程项目的结构越来越复杂,需要接入合适的持续集成流水线形式,才能满足更多变的需求,那么如何优雅地使用CI能力提升生产效率呢?CODINGDevOps微服务项目实战系列第二课《DevOps微服务项目实战:CI进阶用法》将由CODINGDevOps全栈工程师何晨哲老师向大家介绍持续集成流水线的进阶能力,结
Stella981 Stella981
3年前
GitLab+Rancher实践DevOps【转载】
摘要本文描述使用自建GitLab和Rancher实践持续集成/持续交付流水线的过程,并用Rancher实现容器编排和蓝绿发布。GitLab持续集成GitLab持续集成/持续交付流程图:!image(https://docs.gitlab.com/ee/ci/img/cicd_pipeline_infograp
Stella981 Stella981
3年前
Jenkins+Git+Maven简单教程
关于Jenkins1.什么是JenkinsJenkins是一个可拓展的持续集成(CI)引擎(ContinuousIntegrationEngine)。主要用于:  1.持续、自动地构建、测试软件项目 2.监控一些定时执
Wesley13 Wesley13
3年前
GitHub Actions入门
一、一些概念持续集成(Continuousintegration)频繁地向一个共享仓库提交少量代码变更的软件开发实践。使用GitHubActions,可以创建自定义的CI工作流,以自动构建并测试你的代码。从你的仓库中,你可以查看代码变更的状态和工作流中每个操作的详细日志。CI通过提供代码变更的及时反馈来更快地检
Easter79 Easter79
3年前
ThoughtWorks Go基本概念
写在前面:当前最流行的持续集成工具非jenkins莫属,已使用jenkins一年有余,想尝试下别的CI工具,如ThoughtWorksGo,且在尝试时做主要记录并分享,这是首先介绍下基本概念......ThoughtWorksGo是ThoughtWorks公司的一款持续集成和发布的系统。它用一个创新的方法来管理构建、测试和发布过程。
Stella981 Stella981
3年前
Jenkins使用配置
Jenkins是什么?  先了解一下持续集成的概念:持续集成是一种软件开发实践,即团队开发成员经常集成它们的工作,通过每个成员每天至少集成一次,也就意味着每天可能会发生多次集成。每次集成都通过自动化的构建(包括编译,发布,自动化测试)来验证,从而尽早地发现集成错误。而Jenkins就是基于Java开发的一种持续集成
Stella981 Stella981
3年前
Jenkins持续集成与部署
1.1开发写代码的演变好景不长,开发越来越多,代码文件越来越多,每天下班前合并代码时,发现很多合并失败的文件。最后每天加班3小时人工合并代码。解决方法:将合并代码的周期缩短,以前一天,现在一小时,半小时。。。随时随地地将代码合并,这种方法叫做持续集成。1.2持续集成(简称CI)说
Stella981 Stella981
3年前
Jenkins安装使用教程
一、说明持续集成:Continuousintegration,CI。包括两层含义,一是指项目的每个开发人员每天都向项目代码仓库要通过git等提交他们的代码,二是指在代码提交后实现自动化的构建、部署、测试确保提交的代码没有错误或及早发现提交代码中的错误。Jenkins是一个使用java开发、开源、免费、强大的web式持续集成应用程序;常用于
LibraHeresy LibraHeresy
2年前
Taro CI 持续集成框架的配置与使用
痛点使用Taro跨端框架开发小程序时,需要切换三个界面,进行三次操作,才能上传成功,上传代码步骤过于繁琐。且多人开发时,如果想让自己代码生效,需要切换体验版代码为自己上传的版本。vscode打包微信开发者工具上传设置为体验版方案使用微信官方的CI插件包,即