ドキュメント内の Python コードの自動テストを行うCIを構築するためのGitHub Actionsを作りました。Pythonの例で書きますが、どの言語でも使えます。
ワークフロー
以下の流れでドキュメント内のコードのテストを自動化します。
- ローカル
- README内のコードブロックにアノテーション
- 関数定義
- テストコード実装
- CI (GitHub Action)
- 自動テスト
- Markdown Embed Code From File でREADMEにアノテーションされたコードブロックへ埋め込み
- 差分をコミット
こちらで試しています。
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/
ディレクトリに先程定義した関数/クラスのユニットテストを書きます。
from src.mul import multiple
def test_mul():
assert multiple(3) == 9.
ここで、一度ローカルで自動テストを実行しておきます。以下の様に、pytest
でユニットテストを行います。
$ pytest test_src
ただし、今回紹介する方法は、好きな手法でテストを行なえます。
2. CI (GitHub Action)
次のCIをGitHub Actionsで構築します。
- Pythonのコードを自動テスト
- コードブロックの同期
- 差分を自動コミット/プッシュ
先にすべて書くと、先程のディレクトリ構成に以下のGitHub Actions の設定ファイル (.github/workflows/readme-test.yml
) を加えます。
jobs
の部分で何をやっているのか後で順に説明していきます。
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の自動テストの構成をいくつかのせておきます。
- Building and testing Python
- [Poetry] Python Tips: Python のプロジェクトで GitHub Actions を使いたい
- [Pipenv] Github ActionsでPipenv + Pytestの自動テストを行った
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の中で差分をコミットします。
煩雑ですが、以下の処理を行っています。
- ファイルの変更がないか確認 -> なければ終了
- コミット (GitHub Actions用のユーザーアカウントを指定)
- リモートレポジトリへプッシュ (
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製。埋め込むファイルのパスがコードブロック内に入る