この記事は約 5 分で読めます。
筆者プロフィール: ソフトウェアエンジニア。「知った気にならない。いつまでも学び続ける」を信条に、業務と個人開発の両輪で技術を磨いています。AI 駆動開発で複数の個人開発アプリを構築・運用中。
👉 ポートフォリオ: 筆者ホームページ
個人開発の SaaS で 「テストコードの追加・修正を伴わないソースコード変更はコミットしない」 を明文化した話を整理します。運用中のAI業務管理秘書 SaaS 「たすきば Knowledge Relay」 で、半年間 CI red を 1 件も持ち越さなかった運用ルールです。
サービスの機能紹介・画面イメージ・コンセプトは公式プロダクトページをご覧ください。
👉 たすきば Knowledge Relay — 公式プロダクトページ
こんな日、ありませんか?
- 「今日は機能を急ぎたい、テストは後で書く」
- 「小さな変更だから、テストはいらない」
- 「E2E が落ちなければ大丈夫だろう」
こうした判断を許すと、テストのない領域がじわじわ広がります。
- 最初のうちは平気
- 3 ヶ月後にバグが出る
- 6 ヶ月後にリグレッションが頻発する
- 1 年後に「この機能、テストないからリファクタできない」と動けなくなる
これを防ぐため、たすきばは 「テストコードを伴わないコミットを禁止」 をルール化しました。
1. ルールの文言 — CLAUDE.md に明記
CLAUDE.md (Claude Code 運用ガイド) に書いている文言:
## コミットルール
- **テストコードの追加・修正を伴わないソースコード変更はコミットしない**
- コミットメッセージは変更内容を端的に記述する
- コミットは Claude Code が勝手にやらず、必ずユーザに確認してから実施する
- `main` / `master` / `develop` / `release/*` / `hotfix/*` への直接コミットは禁止
1 行目が肝。ソースコード変更 = テストコード変更 が常にペア、というルールです。
2. テスト不要な変更の例外
ルールには例外があります。
| 変更の種類 | テスト要否 |
|---|---|
| 機能追加 (新規 page / route / Service 関数) | ✅ 必須 |
| バグ修正 | ✅ バグ再現テストを必須 |
| リファクタリング (挙動変更なし) | △ 既存テストで十分なら追加不要 |
| 型定義のみの変更 | ❌ 不要 |
| コメント / docs のみの変更 | ❌ 不要 |
| 環境変数 / 設定ファイルのみ | ❌ 不要 |
「ソースコードのロジックを変更する場合」は必須。ロジックに影響しない変更 (リネーム / 型 / docs) は例外。
3. バグ修正には再現テストを必須
バグ修正 PR では、バグを再現するテストケース を必ず追加します。
describe('billing invariant', () => {
it('counter と ApiCallLog SUM が乖離したとき drift audit に記録される (PR #xxx の再現)', async () => {
// バグを再現するセットアップ
// アサーション
});
});
これにより:
- バグの存在が code で明示される (将来の自分への docs)
- 再発防止 (同じバグでテストが落ちる)
- KDD / changelog に紐付けられる
バグ修正の品質を上げる最小限の習慣です。
4. テストカバレッジは KPI にしない
ただし、テストカバレッジ % を KPI にはしていません。
カバレッジ 100% を目指すと:
- 意味のないテストが量産される (テストのためのテスト)
- 重要なロジックに 5 件テスト書くより、雑なロジックに 1 件で 100% を取りに行く
- mock が複雑化する
数字を追うと 本末転倒 になります。
たすきばは「テストコードと一緒にコミットする」をルールにする一方、カバレッジは 目安として参照するだけ です。
5. テストを書きやすい設計が自然と育つ
テストを必須にすると、自然と 「テストを書きやすい設計」 になります。
例:
- 関数の引数に必要な context をすべて渡す (グローバル変数に依存しない)
- 副作用 (DB 書き込み / API 呼び出し) を最外周に閉じ込める
- 引数と返り値で振る舞いが決まる pure な関数を増やす
これが Clean Architecture や Hexagonal Architecture と相性が良いです。
たすきばの Service 層は、context 引数で viewerTenantId / viewerUserId をすべて受け取る設計です。これにより、テストで context を簡単に差し替えられます。
test('listProjects は viewerTenantId のプロジェクトだけ返す', async () => {
await createProject({ tenantId: 'A', name: 'P1' });
await createProject({ tenantId: 'B', name: 'P2' });
const result = await listProjects({
viewerTenantId: 'A',
viewerUserId: 'any',
});
expect(result).toHaveLength(1);
expect(result[0].name).toBe('P1');
});
context をオブジェクトで渡すパターンにしておくと、テストでも本番でも同じ形で呼べる。設計がテストに引っ張られる のはむしろ望ましい副作用です。
6. テストを書く時間の目安
個人開発で「テストを書く時間がない」のは、テストを書かない理由になりません。
目安として:
| 作業 | テスト時間 |
|---|---|
| 1 機能の実装に 1 時間 | テストに 30 分 |
| バグ修正に 30 分 | 再現テストに 15 分 |
「実装時間の 30〜50% をテストに使う」 のが目安です。
最初は遅く感じますが、3 ヶ月後にはリグレッションが減り、開発が 加速 します。
7. AI 駆動開発でも同じルール
たすきばは Claude Code に開発を委ねる時期があります。そのとき、Claude Code に対しても 同じルール を適用します。
CLAUDE.md に明記しているため、Claude Code は機能変更時に必ずテストも追加します。
ユーザ: 「createProject 関数で、空文字 purpose を許可するように変更してください」
Claude: 「実装を変更しました。同時に、空文字 purpose で create できることを verify する
テストを追加しました。」
ルールを文書化しておくと、AI も含めて全員が守れる のがポイントです。
8. ルール違反時の対処
万一、テストを伴わないコミットが入ってしまったら:
1. 即座に気づいたら、別 commit でテストを追加して push
2. PR に test 不足を明示
3. KDD / 再発防止ノートに記録
「未来の自分が見るための docs」 として、ルール違反の事例を残します。これにより、再発時の対処がスムーズになります。
おわりに — 品質を守るのは技術ではなく運用ルール
「品質を守る」のは技術ではなく、運用ルールである。
CI / lint / test framework は道具に過ぎません。
それを 「毎回使う」運用ルール があって初めて、品質が守られます。
| ルール | 効果 |
|---|---|
| テストを伴わないコミットを禁止 | テストのない領域がじわじわ増えない |
| バグ修正には再現テストを必須 | 再発防止 |
| カバレッジを KPI にしない | テストのためのテストを避ける |
| AI 駆動でも同じルール | 全員が守る |
個人開発で品質を維持する最大のコツは、ルールを明文化し、例外を作らず、AI を含めて全員が守る こと。
たすきばが半年間、CI red を 1 件も持ち越さなかったのは、このルールのおかげです。
本記事のルールは、運用中のAI業務管理秘書 SaaS 「たすきば Knowledge Relay」 で実際に運用しているものです。
👉 たすきば Knowledge Relay — 公式プロダクトページ