13
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

PythonAdvent Calendar 2020

Day 5

Github Actionsでドキュメント内のコードのテスト自動化CIを構成する

Last updated at Posted at 2020-12-04

ドキュメント内の Python コードの自動テストを行うCIを構築するためのGitHub Actionsを作りました。Pythonの例で書きますが、どの言語でも使えます。

ワークフロー

以下の流れでドキュメント内のコードのテストを自動化します。

  1. ローカル
    1. README内のコードブロックにアノテーション
    2. 関数定義
    3. テストコード実装
  2. CI (GitHub Action)
    1. 自動テスト
    2. Markdown Embed Code From File でREADMEにアノテーションされたコードブロックへ埋め込み
    3. 差分をコミット

こちらで試しています。

1. ローカル

以下のディレクトリ構成をとることにします。

├── README.md
├── src
│   ├── __init__.py
│   └── mul.py
└── test_src
    ├── __init__.py
    └── test_mul.py

1.1. README内のコードブロックにアノテーション

次のように手順1.1.を紹介した README.md をディレクトリに配置します。

## README.md

README内のコードブロックに"```lang:external/file/path.py```"のようにアノテーションしておきます。

つまり、以下のコードブロックはCIで `src/mul.py` と同期されます:

```python:src/mul.py


> 
>\* CIの度に、上記コードブロックの中身がsrc/mul.pyの中身に置き換わります
\* READMEのレンダリング時にPython用のハイライトがかかります。
\* ファイルが存在しなければCIは失敗となります。

### 1.2. 関数定義

`src/` ディレクトリに埋め込みたいソースコードを通常通り定義します。

ここでは適当な関数を定義します。

```python:src/mul.py
from math import pow

def multiple(x):
    return pow(x, 2)

1.3. テストコード実装

test_src/ ディレクトリに先程定義した関数/クラスのユニットテストを書きます。

test_src/test_mul.py
from src.mul import multiple

def test_mul():
    assert multiple(3) == 9.

ここで、一度ローカルで自動テストを実行しておきます。以下の様に、pytest でユニットテストを行います。

$ pytest test_src

ただし、今回紹介する方法は、好きな手法でテストを行なえます。

2. CI (GitHub Action)

次のCIをGitHub Actionsで構築します。

  1. Pythonのコードを自動テスト
  2. コードブロックの同期
  3. 差分を自動コミット/プッシュ

先にすべて書くと、先程のディレクトリ構成に以下のGitHub Actions の設定ファイル (.github/workflows/readme-test.yml) を加えます。

jobs の部分で何をやっているのか後で順に説明していきます。

.github/workflows/readme-test.yml
name: Test code and embed into README

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main
jobs:
  readme-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
        with:
          persist-credentials: false
          fetch-depth: 0
      - name: Set up Python
        uses: actions/setup-python@v2
        with:
          python-version: 3.7
      - name: Install dependencies
        run: pip install pytest
      - name: Test with pytest
        run: pytest test_src

      - uses: tokusumi/markdown-embed-code@main
        with:
          markdown: "README.md"

      - name: Check modified (success if not changed, failure if the other)
        id: modified
        run: if [[ $(git status --porcelain | wc -l) != "0" ]]; then exit 1; fi
        continue-on-error: true

      - name: Commit files
        if: steps.modified.outcome == 'failure'
        run: |
          git config --local user.email "action@github.com"
          git config --local user.name "GitHub Action"
          git commit -m "Embedding code into Markdown" -a
      - name: Push changes
        if: steps.modified.outcome == 'failure'
        uses: ad-m/github-push-action@master
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          branch: ${{ github.head_ref }}

2.1. 自動テスト

まず、Pythonの自動テストの実行部分を説明します。

自動テストに該当する部分は以下です。

jobs:
  readme-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
        with:
          persist-credentials: false  # 後述
          fetch-depth: 0              # 後述
      - name: Set up Python
        uses: actions/setup-python@v2
        with:
          python-version: 3.7
      - name: Install dependencies
        run: pip install pytest
      - name: Test with pytest
        run: pytest test_src

非常に簡単な構成になっています。例として上記の設定を紹介しましたが、それぞれの開発環境にあわせて自由に自動テストが行えます。

参考までに、GitHub ActionsによるPythonの自動テストの構成をいくつかのせておきます。

2.2. Markdown Embed Code From File でREADMEにアノテーションされたコードブロックへ埋め込み

次に、テストしたコードをREADMEに埋め込みます。

このために、Markdown Embed Code From File というGitHub Actionを作りました。

使い方は、以下の様にターゲットとするREADMEのファイル名を指定するだけです。

      - uses: tokusumi/markdown-embed-code@main
        with:
          markdown: "README.md"

2.3. 差分を自動コミット/プッシュ

変更をレポジトリに反映させるためにCIの中で差分をコミットします。

煩雑ですが、以下の処理を行っています。

  1. ファイルの変更がないか確認 -> なければ終了
  2. コミット (GitHub Actions用のユーザーアカウントを指定)
  3. リモートレポジトリへプッシュ (secrets.GITHUB_TOKEN はデフォルトで設定されているシークレットです)
jobs:
  readme-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
        with:
          persist-credentials: false  # 
          fetch-depth: 0              # 

      # ...

      - name: Check modified (success if not changed, failure if the other)
        id: modified
        run: if [[ $(git status --porcelain | wc -l) != "0" ]]; then exit 1; fi
        continue-on-error: true

      - name: Commit files
        if: steps.modified.outcome == 'failure'
        run: |
          git config --local user.email "action@github.com"
          git config --local user.name "GitHub Action"
          git commit -m "Embedding code into Markdown" -a
      - name: Push changes
        if: steps.modified.outcome == 'failure'
        uses: ad-m/github-push-action@master
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          branch: ${{ github.head_ref }}

このCIがはしると、README.md は以下の様にコードが同期されます。

## README.md

README内のコードブロックに"```lang:external/file/path.py```"のようにアノテーションしておきます。

つまり、以下のコードブロックはCIで `src/mul.py` と同期されます:

```python:src/mul.py
from math import pow

def multiple(x):
return pow(x, 2)


> 
>\* CIの度に、上記コードブロックの中身がsrc/mul.pyの中身に置き換わります
\* READMEのレンダリング時にPython用のハイライトがかかります。
\* ファイルが存在しなければCIは失敗となります。


## おわり

ドキュメント生成ツールだとMarkdownへの外部ファイルからの埋め込みはよくサポートされていますが、READMEのような生のmarkdownを直接編集してくれるものが見つからなかった (GitHub Actionsには) ので作りました。
よければ使ってみて下さい。

## 参考・類似ツール

- [embedmd](https://github.com/campoy/embedmd): golang製。埋め込むファイルのパスがコードブロック内に入る
13
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?