はじめに
pushやcommit前にテストの実行などを強制するツールに、huskyやlint stagedなどがあります。
これらは有用で良い感じなのですが、ローカルに閉じてしまう仕組みです。
チーム開発においては「誰がどこで詰まってるか?」といった状況が見えづらいのが難点です。
「テストやカバレッジをクラウド上(CI)で検証し、その結果をGitHub上で自動的に可視化・共有できたら便利だな~」 と思い、今回の取り組みを試してみました。
今回の構成
今回の構成です。
- GitHub ActionsでCIを構成し、ブランチにpushされたタイミングでカバレッジをチェックするテストを実行します
- GitHubのブランチ保護ルールを使い、mainブランチへのPRが“上記のテストにパスしていること”を必須条件とします
- たぶん良い感じになるはずです
準備
コードを用意する
こんな感じのプロジェクトを用意します。細かい部分は以下で補足します。
github-action-hello-world
├── _test_
│ └── math.test.ts
│
├── .github
│ └── workflows
│ └── node.js.yml
│
├── node_modules
│
├── src
│ └── math.ts
│
├── .gitignore
├── package-lock.json
├── package.json
├── README.md
├── tsconfig.json
└── vitest.config.ts
Vitest(Nodejs)でテストコード用意
本筋ではないので適当に用意します。
src/math.ts
テスト対象コードです。3つの関数を持ちます。
export function add(a: number, b: number): number {
return a + b
}
export function subtract(a: number, b: number): number {
return a - b
}
export function multiply(a: number, b: number): number {
return a * b
}
test/math.test.ts
テストコードです。src/math.ts
内の関数をテストしますが、意図的にカバレッジを下げています。
import { describe, it, expect } from 'vitest'
import { add, multiply } from '../src/math'
describe('math functions', () => {
it('adds two numbers', () => {
expect(add(2, 3)).toBe(5)
})
it('multiplies two numbers', () => {
expect(multiply(4, 5)).toBe(20)
})
})
vitest.config.ts
今回使用しているテスティングライブラリの設定ファイルです。
/// <reference types="vitest" />
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
globals: true, // describe, it, expect をグローバルに使えるように
environment: 'node', // または 'jsdom'(ブラウザをエミュレートする場合)
coverage: {
reporter: ['text', 'json', 'html'],
thresholds: {
functions: 90,
}
},
},
})
細かいところは無視して、大丈夫です。
カバレッジに関数網羅率90%を設定している、以下の部分が大事です!
thresholds: {
functions: 90,
}
その他設定ファイルです。
設定ファイル
{
"name": "vitest-sample",
"version": "1.0.0",
"type": "module",
"scripts": {
"test": "vitest",
"coverage": "vitest run --coverage"
},
"devDependencies": {
"typescript": "^5.0.0",
"vitest": "^3.1.1",
"@vitest/coverage-v8": "^3.1.1"
}
}
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Node",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"types": [
"vitest/globals"
],
"baseUrl": ".",
"paths": {
"@/*": [
"src/*"
]
}
},
"include": [
"src",
"tests"
]
}
カバレッジを測定してみる
npm run coverage
でvitestを実行します
$ npm run coverage
> vitest-sample@1.0.0 coverage
> vitest run --coverage
RUN v3.1.1 /mnt/c/work/repo/github-action-hello-world
Coverage enabled with v8
✓ __test__/math.test.ts (2 tests) 10ms
✓ math functions > adds two numbers 5ms
✓ math functions > multiplies two numbers 1ms
Test Files 1 passed (1)
Tests 2 passed (2)
Start at 11:49:43
Duration 6.91s (transform 510ms, setup 0ms, collect 541ms, tests 10ms, environment 1ms, prepare 3.55s)
% Coverage report from v8
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 77.77 | 100 | 66.66 | 77.77 |
math.ts | 77.77 | 100 | 66.66 | 77.77 | 6-7
----------|---------|----------|---------|---------|-------------------
ERROR: Coverage for functions (66.66%) does not meet global threshold (90%)
ERROR: Coverage for functions (66.66%) does not meet global threshold (90%)
良い感じですね
GitHub Actions用のymlを作成する
.github/workflows/coverage.yml
を作成します
ここらへんを参考に作成しました
name: check-coverage
on:
push:
branches:
- main
- develop
jobs:
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Use Node.js 22.x
uses: actions/setup-node@v4
with:
node-version: '22.x'
cache: 'npm'
- run: npm ci
- run: npm run coverage
特にコメントがない普通?のymlです
Node22を使用しています
gitにpushしてみる
$ git push
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To github.com:suiwave/github-action-hello-world.git
2945d1a..ba771a7 develop -> develop
いざpush!
GitHub上で確認します
良い感じに落ちてますね
developからmainにプルリクを作成してみました
当然、マージできてしまいます。危険!
Githubでブランチに保護ルールを設定する
GitHub Actionsでカバレッジをチェックできるようになりました
しかし、チェックがfailでもマージ可能であることも同時に確認できました
よくないので、ブランチ保護ルールで保護します!!
保護ルールを設定する
設定タブに移動
移動します
Branch設定に移動
移動します。ドキュメントへのリンクもありますね。
ブランチルールセットを追加する
「Add branch ruleset」をクリックします
ルールセット名とルールの有効化を設定する
ルールセット名は適当に「Require coverage checks to pass」
Enforcement statusを「Active」に設定し、ルールを有効化します
バイパスリストと対象ブランチを設定する
検証なのでバイパスリストは空としておきます
対象ブランチは、ルールで守るブランチを指定します
今回はマージを取り込むブランチであるdefaultブランチを指定します
Branch rulesを設定する
いろいろありますね
今回のルールセットは、Coverageのパスのみを要求するものなので、いったんすべてのチェックをオフにします
(直push禁止などは他のルールセットで設定する想定です)
Require status checks to passを設定する
Branch rulesの中の、Require status checks to passが今回追加するルールです
コミットはまずチェックが通る別の参照にプッシュされなければなりません。
mainブランチの更新は、チェックをパスしたブランチのプルリクで行いなさい、的な意味です(た、たぶん・・・)
追加設定を行う
「Require branches to be up to date before merging」のチェックもしておきます。基本ですね
また、Add checksから、作成したGitHubActionsのJobを選択します
ルールセットを作成する
再度設定内容を確認後、「create」をクリックします
喜ぶ
やったールールセットが作成されました
これで、プルリクマージ前に、カバレッジチェックのpassを必須とすることができました。
検証
状況確認
先ほどマージ可能だったプルリクを見に行きます
きたーーーーーーーーーーーーーーーーーーー
Merging is blocked due to failing merge requirements
良い感じですね
パスするブランチを作成してみる
パスするブランチも作ってみます。そこからプルリクをなげます
ブランチ作成
develop
からfeature/remove-subtract-function
ブランチを生やしてみます
$ git checkout -b feature/remove-subtract-function
Switched to a new branch 'feature/remove-subtract-function'
ファイル編集
src/math.ts
テストしていない関数を削除します(テストを増やせよ)
export function add(a: number, b: number): number {
return a + b
}
- export function subtract(a: number, b: number): number {
- return a - b
- }
export function multiply(a: number, b: number): number {
return a * b
}
.github/workflows/coverage.yml
対象ブランチを拡張します
on:
push:
branches:
- main
- develop
+ - feature/*
push結果確認
pushしてGitHubActionsが実行されたことを確認します
きた!無事、対象ブランチが拡張され、またカバレッジチェックもpassしています
プルリク作ってマージできるか確認
プルリクつくって、確認します
はい、チェックパスしてるので、マージ可能です!
ブランチ保護ルールがしっかり働いていそうですね!
感想
普段はCodeCommitやCodebuild,CodepipelineでCI/CDをしているのですが、なんとなくGitHub Actionsに入門してみました。
まずはゆるくCI前のプルリクフェーズで使ってみました。慣れるとActions実行結果画面のUIが良い感じで、良いなと感じました!
CI/CDワークフローなどもおいおい勉強したいです。
今回のリポジトリ