github actionsで自作プログラムの動作確認を少しだけ楽にした話

## あらすじ
- [こちら](https://qiita.com/seigot/items/e16e9d8f6fc04853855f)や[こちら](https://qiita.com/seigot/items/0b42a807217dbe636226)でテトリスを題材にpythonを学べるプログラムを自作していた
- プログラムが大きくなるにつれて動作確認が大変になってきた（ミスも多くなってきた）
- プログラムはgithubで管理しており、`github actions`が便利そうなので動作確認に使った。

## `github actions`とは

> https://docs.github.com/ja/actions/learn-github-actions/understanding-github-actions
> GitHub Actions は、ビルド、テスト、デプロイのパイプラインを自動化できる継続的インテグレーションと継続的デリバリー (CI/CD) のプラットフォームです。 リポジトリに対するすべての pull request をビルドしてテストしたり、マージされた pull request を運用環境にデプロイしたりするワークフローを作成できます。

ワークフローの作り方

> ワークフローはリポジトリ内の .github/workflows ディレクトリで定義され、リポジトリには複数のワークフローを含めることができます。各ワークフローは、それぞれ異なる一連のタスクを実行できます。 たとえば、あるワークフローでは、pull request をビルドしてテストし、別のワークフローでは、リリースが作成されるたびにアプリケーションをデプロイし、さらに別のワークフローでは、新しい issue が開かれるたびにラベルを追加することができます。

## テトリスを題材にpythonを学べるプログラム

以下のようなものを作成していました

>Tetris Game
>プログラミング学習を目的とした、ブロックを操作してスコアを競うゲームです。

https://github.com/seigot/tetris

作成背景は以下の通り

https://qiita.com/seigot/items/e16e9d8f6fc04853855f

https://qiita.com/seigot/items/0b42a807217dbe636226

## 動作確認が大変になってきた

- プログラムをアップデートするにつれて実行オプションが増えてきた（難易度変数、サンプルコード増加、その他）
- 想定すべき実行環境が増えてきた（`ubuntu18.04,20.04`,`python3.x`系）
- プログラムの中身を変えた時に思わぬ箇所に影響が出てバグって動かない事態にしばしば直面
- ほぼ全てに影響するバグが潜在した場合、顕在化するまで潜伏期間１週間〜掛かり気付いた時には超特急で修正必要になる事にしばしば直面、夜中に泣きながら修正することが発生して精神衛生上良くない。

## 以下のような対策をした

- プログラムを変えた際は、主要な実行オプションは網羅的にテストする。
- 細かい動作はテストやりきれない事があるが、クラッシュしない確認だけでも平穏が訪れる。
- AIが動いている部分については学習と評価を簡易でも良いので実行しておく。

## 実際に使った`github actions`のワークフロー（`.github/workflow/test.yaml`）

`push時`及び`pull request時に`github actionsを発動させ、致命的な不具合が発生していないことのテストを行った。


### `github actions`の設定ファイル


```yaml:.github/workflow/test.yaml
name: test-ubuntu
      
on: 
# push/pullrequest時にci実行
    push:
      branches: [master]     # masterへのpush時に発動
    pull_request:
        paths-ignore:        # ここのファイルの更新時は不要なのでworkflow発動させない
          - '.gitignore'
          - 'LICENSE'
          - '**.md'

# 環境変数
env:
  BRANCH_NAME : "master"　　　# workflow上で使う環境変数

jobs:
  build:

    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ['3.9', '3.10'] # "python-version"の変数リスト('3.9', '3.10'で微妙に動作に違いが出たので念のため両バージョンでチェック)

    steps:
      - name: checkout repository
        uses: actions/checkout@v3
        with:
          path: target_dir
      - name: Set up Python ${{ matrix.python-version }} # "python-version"の変数リスト分workflowを立ち上げる
        uses: actions/setup-python@v4
        with:
          python-version: ${{ matrix.python-version }}
      - name: Install dependencies # 依存ライブラリをインストールしておく
        run: |
          sudo apt-get update
          python -m pip install --upgrade pip
          pip install flake8 pytest
          sudo apt-get install -y python3-pip
          pip3 install --upgrade pip
          sudo apt-get install -y git
          sudo apt-get install -y jq

      - name: tetris_sample        # 基本となるサンプルコードの確認
        working-directory: target_dir
      # tetrisが動くかどうか確認(SCOREが入ればOK)
        run: |
          # QT display off
          export QT_QPA_PLATFORM=offscreen
          # run tetris
          if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
          python start.py -m sample -t 3 -f $HOME/result.json
          # check result
          jq . $HOME/result.json
          SCORE=`jq ."judge_info"."score" $HOME/result.json`
          echo "SCORE: $SCORE"
          if [ $SCORE -lt 0 ]; then echo "error";exit 1; fi

      - name: tetris_DQN_sample    # DeepQNetwork版サンプルコードの動作確認
        working-directory: target_dir
        run: |
          # QT display off
          export QT_QPA_PLATFORM=offscreen
          # run tetris
          if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
          if [ -f requirements.pytorch.txt ]; then pip install -r requirements.pytorch.txt; fi
          python start.py -m predict_sample -l 2 -t 5 --predict_weight weight/DQN/sample_weight.pt -f $HOME/result_dqn.json
          # check result
          jq . $HOME/result_dqn.json
          SCORE=`jq ."judge_info"."score" $HOME/result_dqn.json`
          echo "SCORE: $SCORE"
          if [ $SCORE -lt 0 ]; then echo "error";exit 1; fi

      - name: tetris_MLP_sample    # Multilayer perceptron版サンプルコードの動作確認
        working-directory: target_dir
        run: |
          # QT display off
          export QT_QPA_PLATFORM=offscreen
          # run tetris
          if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
          if [ -f requirements.pytorch.txt ]; then pip install -r requirements.pytorch.txt; fi
          python start.py -m predict_sample2 -l 2 -t 5 --predict_weight weight/MLP/sample_weight.pt -f $HOME/result_mlp.json
          # check result
          jq . $HOME/result_mlp.json
          SCORE=`jq ."judge_info"."score" $HOME/result_mlp.json`
          echo "SCORE: $SCORE"
          if [ $SCORE -lt 0 ]; then echo "error";exit 1; fi

      - name: tetris_DQN_sample3  # DeepQNetwork版サンプルコード2の動作確認
        working-directory: target_dir
        run: |
          # QT display off
          export QT_QPA_PLATFORM=offscreen
          # run tetris
          if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
          if [ -f requirements.pytorch.txt ]; then pip install -r requirements.pytorch.txt; fi
          python start.py -m predict_sample3 -l 2 -t 5 --predict_weight weight/DQN/sample_weight.pt -f $HOME/result_dqn.json
          # check result
          jq . $HOME/result_dqn.json
          SCORE=`jq ."judge_info"."score" $HOME/result_dqn.json`
          echo "SCORE: $SCORE"
          if [ $SCORE -lt 0 ]; then echo "error";exit 1; fi
```

## 工夫したことがあるとすれば

### GUI表示の無効化

今回ディスプレイを使うのは`pyqt`のみだったので`export QT_QPA_PLATFORM=offscreen`でQT displayオフにした

```yaml:
# QT display off
export QT_QPA_PLATFORM=offscreen
```

場合によっては仮想ディスプレイを使っても良いかもしれない（Qt以外を使う場合など）

```yaml:
      # xvfbのインストールと起動
      - name: Xvfb install and run
        run: |
          sudo apt-get install -y xvfb
          Xvfb -ac ${{ env.DISPLAY }} -screen 0 1280x780x24 &
```

> [https://github.com/marketplace/actions/url-checker](https://github.com/marketplace/actions/url-checker)

### エラー発生時（クラッシュなど）はエラー側に倒しまくる

`echo $?`や結果が期待通りかどうか（スコアが更新されているか等）を少しでもチェックして、期待通りでなければエラー側に倒して気づけるようにする。が現状は努力目標となっている。

### `ubuntu`だけでなく`windows`の`powershell`でも動くことを想定してworkflowを記載

自分で使う限りではubuntuマシンのみを想定すればよいが、周囲に布教させようとする場合はたとえ開発系の職場であってもwindowsマシン（のpowershell）で動かすことを想定せざるを得ない場合が往々にして存在する。なぜならば誰もが自宅にubuntuマシンを所持しているわけではないためである。（体感8割の人が自宅にubuntuマシンを所持していない。ubuntuマシンを自宅に所持している人はどちらかというとマイノリティのはずである。一般的にはそのはずである。）

windowsマシン（のpowershell）を想定して`github actions`を記載する場合は以下のように`jobs:`欄に`runs-on: windows-latest`と記載すればOK。
ただし以降の構文は`powershell`ライクな感じになるので気をつける必要がある。

> https://github.com/seigot/tetris/blob/master/.github/workflows/test-windows.yaml
>```yaml
>...
> jobs:
>  build:
>    name: MSBuild
>    runs-on: windows-latest
>...
>```


## 今後の課題

- テストを拡張させたいが、仮想マシンの月当たりの利用時間に制限がある。必要なテスト優先で追加する。
- ~~長時間の処理が必要な場合は、別途テスト用のPCを用意もしくは`github`に課金する（テストしないよりはまし、時間リソースは大切である）~~ [パブリックリポジトリの場合はCI/CD時間は無料でした。githubさんありがとうございます。](https://github.co.jp/pricing.html)
- `github actions`のワークフローの良い書き方、ベストプラクティスを継続して調べる。

## 参考
[https://github.com/seigot/tetris](https://github.com/seigot/tetris)
[プログラミング学習を目的とした、ブロックを操作してスコアを競うゲームをpythonで作成する試み](https://qiita.com/seigot/items/a1a7b80ef6f4142ae602)
[Xvfb を使って仮想ディスプレイを作る](https://blog.amedama.jp/entry/2016/01/03/115602)
[https://github.com/marketplace/actions/url-checker](https://github.com/marketplace/actions/url-checker)