知识连线:从集成测试到 Linux 信号

Posted 云物互联

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了知识连线:从集成测试到 Linux 信号相关的知识,希望对你有一定的参考价值。

作者简介:郑越,资深 OpenStack 云计算研发工程师,社区贡献者。


引子

在软件研发领域,CI/CD 从来都是热门话题。在我个人接触软件开发的初始(六/七年前),CI/CD 还只是运维人员的事,单纯的开发人员很少接触。但随着 DevOps 理念,容器技术及微服务架构的大行其道,CI/CD 任务从开发整体过程中前移,逐渐变成了开发人员的本职工作。CI/CD 是个很大的话题,我们不展开,本篇文章的讨论范围,仅限于本人实践中与 CI、Linux 信号相关的点滴知识。

单元测试与集成测试

在软件开发过程中,开发人员首先需要面对的是什么?我认为是测试(抛开需求、设计等),合理与完善的测试是软件质量的基础保障。无论是 TDD/BDD/DDD 等等哪种开发模式,单元测试与集成测试都是绕不开的,那么两者的区别是什么呢?

首先,侧重点不同:

  • 单元测试侧重代码逻辑/具体功能、算法的正确性,往往是非常深入的细节,外部依赖(如数据库访问、交互等)不是单元测试的重点;


  • 而集成测试侧重功能模块之间的联动,强调程序组件接口调用的正确性;

然后,由于侧重点不同,测试的具体方法也就不一样了:

  • 在单元测试中,我们往往把代码逻辑的外部依赖(输入输出、数据库查询、RPC 调用等)按照预先设计好的接口进行 Mock,提供固定格式的测试数据,专注于具体代码逻辑及算法,编写各种测试用例,以求尽量完整覆盖不同的逻辑分支与不同的输入条件;

  • 而集成测试时,一般把各个组件按照设计好的调用关系组合起来,从整体的功能出发做完整流程的测试,通常不再使用 Mock 数据,而是使用真实的输入输出和中间件进行测试。负责串联起整个流程的,有时候是对外暴漏的 API,有时候直接就是 UI,需要 Mock 的就是 API 的输入参数或者用户界面操作。

具体怎么做?

对于单元测试,我们需要掌握各种 Mock 技术:Mock 外部函数,Mock 输入数据,以及 Mock Libiray。总之 Mock 是门艺术,不要轻视。

集成测试呢?在容器技术流行之前,集成测试是困难的,需要搞定各种中间件(数据库、消息队列、缓存),还有各种上下游系统,往往需要开发、运维等人员参与,多部门联动也是常事。但有了容器之后,只要搞到各个组件的镜像,开发人员只需要定义依赖关系,就可以搞定整个集成环境。当然,需要我们熟练掌握容器的使用以及 Shell 等多种自动化技术。

比如一套典型的由 Flask + Celery 实现的系统,其组件大概包括:

  • Web-API

  • DataBase

  • Broker(rabbitMQ/Redis)

  • Celery-worker

那么我们可以这样组织容器:

  • 中间件服务:

 
   
   
 
  1. services:

  2.    mysql:

  3.      image: mysql

  4.      ...

  5.    rabbitmq:

  6.      image: rabbitmq

  7.      ...


  • 应用组件服务:

 
   
   
 
  1. services:

  2.     web_api:

  3.        image: xxxxxx

  4.     worker:

  5.        image: xxxxxx

只要搞定了服务镜像,然后需要做的就是针对分组做相应的配置及初始化,那么集成环境基本搞定。

接下来就开始集成测试,Python 下当然还是使用 unittest 模块来组织测试用例,使用 Mock 数据。模拟用户登陆、资源的 CRUD 等等一系列 API 动作,组件间合作得怎么样就很清楚了。

测试覆盖率

获取集成测试中的代码覆盖率,Python 下最常用的当属 coverage 模块,我们可以利用 coverage 的 run 命令在各个容器中启动相应服务,在集成测试跑完后收集各个服务生成的 coverage 文件,最后再合并为总体的覆盖率文件。

这个实现思路,从理论上看没什么问题,但在具体实践时还确实让我困扰了一番。下面具体介绍这个困扰以及解决的过程,同时进入文章的另一个主题:Linux 系统信号。

完成集成测试并获取覆盖率:

  • 启动服务:

 
   
   
 
  1. docker exec -id web_container_id bash -c "coverage run -m web_api"  

  2. docker exec -id worker_container_id bash -c "coverage run -m worker"

  3. # -d 让任务后台执行,不阻塞终端的后续命令


  • 然后跑测试用例:

 
   
   
 
  1. docker exec -i web_container_id bash -c "coverage run -m test"

我们预期测试用例会在跑完后自动停止,然后生成相应的 coverage 文件。但问题是:服务进程一直在后台运行,该怎么停止服务并获得 coverage 文件呢?

我一开始想到了使用 docker restart 或者 docker stop 将整个 container 将服务进程杀掉,但实际的情况是这样做会导致无法生成 coverage 文件。这样做显然是不可行的,没关系我们再想别的办法,比如试试先把进程杀掉!

  • 首先获取服务进程 ID:

 
   
   
 
  1. web_server_id = 'docker exec -i web_container_id bash -c "ps -ef |grep web_api | awk '{print $2}'"'

  2. worker_server_id = 'docker exec -i worker_container_id bash -c "ps -ef |grep worker | awk '{print $2}'"'


  • 然后 kill 掉进程:

 
   
   
 
  1. docker exec -i web_server_id bash -c "kill ${web_server_id}"

很可惜,这种方法的结果同样悲剧,并没能成功生成 coverage 文件。怎么办 ?!

回过头来,我突然想到:使用 ctrl+C 杀掉前台进程是可以获得 coverage 文件的,那么我们能不能模拟出 ctrl+C 呢?

其实答案很简单,使用 kill -SIGINT PID 就可以模拟 ctrl+C(本人愚钝,在同事指导下才了解这种操作)。而且不仅是 ctrl+C 这种键盘操作的退出,kill 还支持很多类型的 Linux 信号。可以使用 kill -l 指令查看,常用的 kill -9 也属其一。

在 Linux 系统中,信号是一种重要的进程间的通信机制,用途广泛,相关的知识点暂时不再展开。 在我们自己的应用程序中,可以利用 signal 库来处理信号,需要注意的是:系统中的 SIGKILL 和 SIGSTOP 信号是无法被应用程序捕获的。

总结

以上,我们梳理了单元测试和集成测试,介绍了一些利用容器进行 CI 的思路,以及一些实践过程中的小细节,希望对读者有所帮助;涉及的知识点不少,不过都是一带而过,今后有机会再逐个深入探讨。

- END -


技术即沟通

化云为雨,落地成林

以上是关于知识连线:从集成测试到 Linux 信号的主要内容,如果未能解决你的问题,请参考以下文章

漫画|Linux 并发竞态互斥锁自旋锁信号量都是什么鬼?

Linux驱动实践:中断处理函数如何发送信号给应用层?

Linux驱动实践:中断处理函数如何发送信号给应用层?

Linux - 信号:错误:非静态成员函数的使用无效

谱分析中窗的选取

随手记——在Linux下如何测试代码执行时间