GitLab CIでflake8とpytestを実行

# この記事で取り扱うこと
- GitLabのパイプラインで`flake8`と`pytest`を実行する
- 上記の実行タイミングをmerge request提出時に変更する

# この記事を書いたきっかけ
静的解析やフォーマッターの利用、テストコードを書くことはソフトウェアの品質を担保する手段として有用なのは頭ではわかっていますが、なかなかそれをチームでの開発に導入することに難しさ(というか単純にどうすればいいんだろうという気持ち)を感じていました。

普段の業務でGitLabを使っていることもあり、色々調べるうちに難しいことはおいておいて簡単な仕組みならすぐに作れるんじゃないかと思い、簡単にまとめてみました。

GitLabの登録さえすればFreeプランで実行可能なのですぐに試すことはできると思います。

# 実行環境
- GitLab.com(クラウドサービスの方です)
- GitLab Shared Runner

## 1. 環境構築およびFlake8によるチェックの実施

### 1-1.リポジトリをGitLabに作成する
一連の作業を実施するためのリポジトリをGitLabに作ります。プライベートで大丈夫です。

![](https://i.imgur.com/YdhMl2b.png)

### 1-2. gitlab-ymlの追加およびPUSH
ステージ分けの概念もない極めてシンプルなものです。
Python3のイメージを引っ張ってきて、flake8をsrcディレクトリは以下のコードに対して実行します。

```yml
image: python:3
 
before_script:
  - pip install flake8
 
test:
  script:
    - flake8 src/
```

PUSH後にCI/CDを確認してみると、パイプラインが稼働し、flake8による解析が実行されていることが確認できました。

![](https://i.imgur.com/nHwyvP0.png)



![](https://i.imgur.com/DlbhpEe.png)



## 2. Testの追加
flake8の実行は上記で終わったので、次にpytestによるテストの実行をやってみます。

### 2-1. テストの作成およびローカルでの実行
以下のようにtest用のファイルを分けたディレクトリ構成でテストコードを作ってみます。

```
.
├── __init__.py
├── poetry.lock
├── pyproject.toml
├── src
│   └── main.py
│   └── __init__.py
├── tests # 追加
│   └── test_main.py # 追加
│   └── __init__.py # 追加
└── venv
```

テストコードの中身は以下の通りです。
内容は今回あまり気にしていないので、かなり適当です。

```python
import sys, os
current_path = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, current_path + '/../')

from src import main

def test_add():
    result = main.add(3, 5)
    expected = 8

    assert result == expected

def test_substract():
    result = main.substract(10, 5)
    expected = 5

    assert result == expected

def test_multiply():
    result = main.multiply(4, 5)
    expected = 20

    assert result == expected
```

作成したテストをローカルで実行して全てパスすることが確認できました。

```
$ pytest tests   
======================== test session starts ========================
platform darwin -- Python 3.7.6, pytest-5.3.2, py-1.8.1, pluggy-0.13.1
collected 3 items                                                   

tests/test_main.py ...                                        [100%]

========================= 3 passed in 0.01s =========================
```


### 2-2. カバレッジの計測

pytest-covを使ってカバレッジの計測もやってみます。

```shell=
$ pytest --cov=src tests/  
======================== test session starts ========================
...
collected 3 items                                                   

tests/test_main.py ...                                        [100%]

---------- coverage: platform darwin, python 3.7.6-final-0 -----------
Name              Stmts   Miss  Cover
-------------------------------------
src/__init__.py       0      0   100%
src/main.py           6      0   100%
-------------------------------------
TOTAL                 6      0   100%


========================= 3 passed in 0.03s =========================
```


これだけだとつまらないので、main.pyに新しいコードを追加して意図的にカバレッジを下げてみます。


- 追加したコード

```python
...
def divide(x, y):
    return x / y
```

- pytest-covの出力結果

```shell=
% pytest --cov=src tests/   
============================================ test session starts ============================================
...
collected 3 items                                                                                           

tests/test_main.py ...                                                                                [100%]

---------- coverage: platform darwin, python 3.7.6-final-0 -----------
Name              Stmts   Miss  Cover
-------------------------------------
src/__init__.py       0      0   100%
src/main.py           8      1    88%
-------------------------------------
TOTAL                 8      1    88%


============================================= 3 passed in 0.03s =============================================

```

### 2-3. GitLab CI/CDでの実行
yamlファイルに新しくpytest-covの実行を追加してPUSHします。


```yaml
image: python:3
 
before_script:
  - pip install flake8 pytest pytest-cov
 
test:
  script:
    - flake8 src/
    - pytest --cov=src tests/
```


コードをPUSHしてパイプラインの実行結果を確認すると、以下の通り新しく追加したコードでflake8によるチェックに引っかかったためパイプラインがとまりました。
![](https://i.imgur.com/QDZsrlM.png)

再度コードを修正してPUSHします。

![](https://i.imgur.com/k7tWxk7.png)

今度は正常に終了しました。


### 2-4. パイプラインにカバレッジの表示を追加
カバレッジを計測してもいちいちJOBのログを見に行かなければいけないので、
GitLabの機能を使ってカバレッジを表示するように対応します。

具体的にはGitLabの設定ページよりテストカバレッジ解析の機能を使います。
正規表現を使ってログからカバレッジ率を表示する機能みたいですが、pytest-covの場合は例の中にあるのでそのままコピペして保存します。

![](https://i.imgur.com/gPDSgoO.png)

コードを再度Pushしてみると以下の通りカバレッジ率が表示されていることが確認できました。

![](https://i.imgur.com/hhHugcQ.png)


## 3. パイプラインの実行タイミングを変更

今はmasterに直接PUSH→パイプラインの実行という流れになっていますが、
実際の開発ではマージリクエスト等でレビューを挟むことが多いと思います。

なので、マージリクエストの提出時にパイプラインを実行して、flake8による解析とpytestによるテストが通った場合のみレビュー、マージという流れを構築してみたいと思います。


### 3-1. yamlの修正

新しいブランチを切って以下の通りyamlファイルの内容を修正します。
修正点はonlyでmerge_requestを指定するだけです。

詳細はGitLabのページより以下を参照ください。
[Pipelines for Merge Requests](https://docs.gitlab.com/ee/ci/merge_request_pipelines/)

```yaml
image: python:3
 
before_script:
  - pip install flake8 pytest pytest-cov
 
test:
  script:
    - flake8 src/
    - pytest --cov=src tests/
  only:
  - merge_requests
```

プッシュしたら、まずこれまでと違いパイプラインが稼働していないことを確認します。

![](https://i.imgur.com/3UZ5jPp.png)


### 3-2. merge requestの実行
新しく切ったブランチからmasterへのマージリクエストを提出します。


![](https://i.imgur.com/FtF7vxX.png)


#### マージリクエスト提出後(パイプライン稼働中)
以下の通りパイプラインが稼働していることが確認できました。
通常では表示されない「パイプラインが成功したときにマージ」というボタンが見えます。

![](https://i.imgur.com/2ICdG80.png)

#### マージリクエスト提出後(パイプライン稼働後)
パイプライン稼働中から表示が変わってパイプラインの実行結果(成功)とカバレッジが出力されています。


![](https://i.imgur.com/yl6hQzx.png)


### 3-3. マージリクエストの実行(失敗編)

意図的に失敗するテストケースを追加して新しいコミットをPUSHしてみます。
以下の通り、新規マージリクエストだけでなく、追加でのコミットでもパイプラインが稼働することが確認できました。

![](https://i.imgur.com/NvN6PJh.png)

パイプラインの稼働後は以下の通りに表示が変更されました。
現在の設定(デフォルト)ではパイプラインの稼働が失敗してもマージが可能です。

![](https://i.imgur.com/OWFytjF.png)

GitLabではマージはパイプラインが成功したときのみ可能という設定が可能みたいなので、
「Pipelines must succeed」にチェックを入れて設定を保存します。


![](https://i.imgur.com/5xe73WI.png)


すると以下の通り、マージが不可能になりました。
運用をどうするかはプロジェクト次第だと思いますが、静的解析とテストに通過したコードのみマージされるという決まりがあれば品質を担保しやすいのではないでしょうか。

![](https://i.imgur.com/LrEdkPd.png)