阿猫的博客

阿猫的博客

Python CI流程踩坑

2022-01-18

背景

由于最近开始做毕业设计,准备是先做一个轻量级的爬虫框架(类似Scrapy),接触了一下Python的CI流程。

需求概述

正好最近实习也在研究单元测试的内容,所以单元测试是一定要做的,同时要算出单元测试的覆盖率,再同时需要保证代码里面没有smell或者安全隐患。

因此选用的框架如下

  • 单元测试:pytest
  • 单元测试覆盖率:coverage
  • 代码质量检查、可视化:SonarQube

其他工具的话,git和CI/CD依然使用OneDev。

总的项目可以看medusa。CI/CD相关的文件在.onedev-buildspec.yml

最终方案(tl;dr)

首先肯定还是先给项目上git,这部分轻车熟路不多解释了。完事以后我们得给他准备一个CI流程用的docker镜像,这部分是踩坑重点,先简单讲讲最终的方案,后面再补上踩坑的流程。最后代码检查的部分也比较简单,会简单略过。

最终效果:

CI流程docker镜像构建

简单分析一下这个镜像需要干些什么:

  • 提供一个python环境
  • 安装好项目的依赖
  • 提供单元测试工具

众所周知Python的环境是个非常让人蛋疼的事情,根本没有完美的迁移方案(docker除外)。现在介绍一下我摸索出来的这么一个方法。

  1. Docker Hub 找到心水的python版本镜像,例如我这里选择3.7.11
  2. 手工写一份requirements.txt,只包含最需要的依赖并指明版本(例如这里的pytest==6.2.4),一些比较通用(就是版本上下兼容很好,或者其实你无所谓他什么版本的包))可以不写版本号;
  3. pip install -r requirements.txt

写成Dockerfile是这个样子

FROM python:3.7.11
COPY requirements.txt .
RUN pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple/

完事以后因为Windows上docker操作不方便,写个流程让OneDev自己去构建、推送镜像。

单元测试、覆盖率计算

这部分就颇为简单,确保依赖中有pytest-cov就可以直接运行py.test --cov=src,就可以同时运行单元测试和计算覆盖率。后续再运行coverage xml -o coverage.xml即可输出覆盖率计算结果(这一步输出的xml后面SonarQube会用到)。

SonarQube扫描

这一步非常方便,直接使用SonarQube提供的sonar-scanner-cli即可。直接就有镜像(sonarsource/sonar-scanner-cli)可以用。

sonar-scanner \
  -Dsonar.projectKey=medusa \
  -Dsonar.sources=. \
  -Dsonar.host.url=xxx \
  -Dsonar.login=xxx \
  -Dsonar.branch.name=@branch@ \
  -Dsonar.language=python \
  -Dsonar.sourceEncoding=UTF-8 \
  -Dsonar.python.version=3 \
  -Dsonar.python.coverage.reportPaths=coverage.xml

几个字段说明一下

  • sonar.branch.name:配合社区插件,实现社区版也能分branch
  • sonar.python.coverage.reportPaths:填上面输出coverage.xml的路径,给SonarQube分析覆盖率用

目前的CI流程就是这样,后续还有待完善。

踩坑流程

坑点1:Anaconda环境迁移

之前专门写过一篇博客讲Anaconda环境的配置,以为一劳永逸解决99.99%的Python环境问题了,没想到在这里还是翻了车。

原来的方案是:

# 在源机器上运行下面的命令
conda env export > environment.yml
# 在目标机器上运行下面的命令
conda env create -f environment.yml

一般来说就能原封不动把环境迁移过去了,一般指的是windows往windows里迁移,linux往linux里迁移。以上方式适合导出同操作系统和架构的环境,能够保证依赖能原封不动安装。在不同操作系统/架构间迁移时,可能会由于系统/架构原因导致依赖有所不同,不能使用该方式导入。

当然对应这种情况也有勉强能用的方案,也是推荐的方式——手动创建environment.yml,手动输入需要的主要依赖和其版本(如tensorflow、python和其版本号)。例子如下:

name: medusa
channels:
  - defaults
dependencies:
  - coverage=5.5
  - pytest=6.2.4
  - pytest-cov=3.0.0
  - python=3.7.11
  - requests=2.27.1

参考资料:How to share conda environments across platforms

坑点2 :Anaconda在OneDev里不能用

好不容易解决依赖问题,发现在OneDev里面不能切换环境,只好改用pip的方案