互联网软件的开发和发布,已经形成了一套标准流程,最重要的组成部分就是持续集成(Continuous integration,简称CI)。
持续集成的好处大概可以概括为两点:
1)快速发现错误
每完成一点更新,就集成到主干,可以快速发现错误,定位错误也比较容易。
2)防止分支大幅偏离主干
如果不是经常集成,主干又在不断更新,会导致以后集成的难度变大,甚至难以集成。
根据持续集成的设计,代码从提交到生产,整个过程有以下几步:
流程的第一步,是开发者向代码仓库提交代码。所有后面的步骤都始于本地代码的一次提交。
代码仓库对commit操作配置了钩子,只要提交代码或者合并进主干,就会跑自动化测试。
测试有好几种:
第一轮至少要跑单元测试。
交付后,要先进行构建,再进入第二轮测试。在该阶段我们使用的工具为Jenkins。
第二轮是全面测试,单元测试和集成测试都会跑。有条件的话,也要做端对端测试。所有测试以自动化为主,少数无法自动化的测试用例,就要人工跑。
通过了第二轮测试,当前代码就是一个可以直接部署的版本。将这个版本的所有文件打包存档,发到生产服务器。
一旦当前版本发生问题,就要回滚到上一个版本的构建结果。
目前的工作中,测试人员负责单元测试的自动生成以及集成测试用例的编写工作。出于代码安全方面的考虑,测试人员是不具有对应代码库的提交权限的。如果对应的测试代码不提交到代码库中,那么持续集成的第一轮测试中的单元测试阶段就不具备条件。为了解决上述的问题。我们做了一系列的尝试工作。
我们在做单元测试或集成测试的初期,测试人员先本地编写、运行、调式测试用例,然后统一由研发人员来提交代码库中。这种方式在测试用例较少的初期还是可以接受的,但是随着用例数量的增加,维护成本和沟通成本也随之增大,关键是还无形中增加了研发人员的工作量。
一方面为了解决无权限提交测试代码,另一方面为了减少研发人员不必要的工作。后续我们专门为了提交测试代码而在研发代码的基础上做了代码库的派生,这样测试人员就具有了派生代码库的所有权限。
代码库提前权限的问题解决后,这种方式带来的另一个问题就是如何保持派生库与原生代码库的一致性问题。现在的解决办法是手工的方式从原生代码库上拉取到提交的代码,然后再次提交到派生代码库。这样就解决了两个代码库版本不一致的问题。这种方式也存在一定的问题,那就是不能够及时的拉取到最新的代码。在我们执行单元测试的时候,被测试的版本很有可能是滞后的代码版本。
目前我们正在做调研,想通过其他的方式来解决手动同步代码库的问题。想到的方案是,每次原生代码库中有代码的提交时,自动触发代码同步的操作。这样一来,就可以解决派生代码库相对滞后的问题。
市面上已有很多开源持续集成工具,例如我们熟悉的Jenkins,还有TeamCity、Travis CI、GO CD、Bamboo、Gitlab CI、CircleCI等等。
京东质量管理平台(Qone)是运营质量部自主研发的一套综合性的持续集成系统,该系统不仅可以实现项目,人员以及工时的管理,同时还可以实现持续集成,持续部署和持续交付等等功能。不仅可以做到与京东现有的项目管理与自动打包系统做到无缝对接,而且还与Jenkins做到数据同步。充分利用Jenkins现有的功能。只需要在qone系统中做简单的配置,便可以每天定时执行单元测试用例,同时也可以生成单元测试覆盖率报告。
考虑到Jacoco是一个开源的Java代码覆盖率工具,Jacoco可以嵌入到Ant 、Maven中,并提供了EclEmma Eclipse插件,也可以使用JavaAgent技术监控Java程序。很多第三方的工具提供了对Jacoco的集成,如sonar、Jenkins等。因此在多代码覆盖率统计时,我们使用Jacoco作为我们的单元测试覆盖率统计工具。
在做统计时,需要在maven的pom文件中添加如下配置项即可。
1. <plugins>
2. ...
3. <plugin>
4. <groupId>org.jacoco</groupId>
5. <artifactId>jacoco-maven-plugin</artifactId>
6. <version>0.8.1</version>
7. <executions>
8. <execution>
9. <id>pre-test</id>
10. <goals>
11. <goal>prepare-agent</goal>
12. </goals>
13. </execution>
14. <execution>
15. <id>post-test</id>
16. <phase>test</phase>
17. <goals>
18. <goal>report</goal>
19. </goals>
20. </execution>
21. <execution>
22. <id>post-test-aggregate</id>
23. <phase>test</phase>
24. <goals>
25. <goal>report-aggregate</goal>
26. </goals>
27. </execution>
28. </executions>
29. </plugin>
30. ...
31.</plugins>
每次执行完单元测试,会把执行结果以邮件的形式发送给相关人员。
同时我们会以一周为一个周期,统计单元测试的执行情况,供相关人员做参考。
目前,测试人员可以通过物流研发部自主研发的”玲珑”系统进行查看每次单元测试执行的结果,这些结果包括了单元测试的执行结果以及代码的覆盖率结果。
通过单元测试和集成测试环节,在测试过程中发现了近200个有效的bug,随着单元测试涉及到的跳线逐渐增加,该方面的bug数量也在逐渐增加。因此在保证上线质量方面,单测起到了至关重要的一个环节。
单元与集成测试的通过率和覆盖率在实际测试中具有很强的指导意义,它可以指导我们那些代码没有做测试覆盖。但是,一味的追求较高的代码覆盖率和通过率其实就失去了代码覆盖和测试通过率的本质意义。
测试覆盖是一种学习手段,学习为什么有些代码没有被覆盖到,以及为什么有些代码变了测试却没有失败。理解“为什么”背后的原因,程序员就可以做相应的改善和提高,相比凭空想象单元测试的有效性和代码的好坏,这会更加有效。
后续在保证一定代码覆盖率的基础上,我们会把重点放在代码变异测试(Mutation Test)上。通过对代码变异测试的应用,来找到一些提高测试和代码质量的方法。