| name | process-static |
| description | 機能の正しさより外側にある、プロセス運用・静的検査・分類に収まりにくい観点を扱う。 test-catalog の手法カタログの一部。群B(BDD/Gherkin、ATDD、CI自動実行、シフトレフト/シフトライト、 カナリアリリース、ブルーグリーン/A-B、フィーチャーフラグ段階検証、合成監視、 ミューテーション/カバレッジのCIゲート、テストデータ管理、フレークテスト対策)、 群C(静的解析Linter/型チェッカ、コードレビュー、複雑度メトリクス)、 群D(冪等性テスト、並行性/レースコンディションテスト、境界外/極値・ネガティブテスト) を検証したい、または割り当てたいときに使う。 通常は test-catalog スキルの索引経由で手法が選定された後にこのスキルを直接参照する。
|
| disable-model-invocation | true |
プロセス、静的、その他のテスト
機能の正しさより外側にある観点のうち、開発プロセスに組み込むテスト戦術、実行せずに欠陥を見つける静的テスト、そして分類に収まりにくいその他の観点を集める。
左(設計)へ寄せるか右(本番)へ寄せるかの戦術、コードを読んで欠陥を捕まえる静的テスト、冪等性や並行性といった現場頻出の観点を扱う。
項目が多いため大半は概要と目的だけを簡潔に記し、欠けやすい代表的な観点(BDD、冪等性)にだけ TypeScript の最小例を厚く添える。
品質特性(ISO/IEC 25010)に沿う非機能テストは、測定系(性能、負荷、ストレス、スパイク、ソーク、スケーラビリティ、キャパシティ)を nonfunctional-perf.md に、品質保証系(セキュリティ、a11y、互換性、信頼性、耐障害性、コントラクト、データ品質マイグレーション)を nonfunctional-attributes.md にまとめた。
目次
群B: プロセスと運用に紐づくテスト戦術
テスト技法というより、開発と運用のどこにテストを差し込むかの戦術。
左(設計)へ寄せるか右(本番)へ寄せるかが軸になる。
- BDD(Gherkin/Cucumber):振る舞いを自然言語に近い Given-When-Then で記述し、仕様と実行可能テストを一致させる。関係者の認識を揃えるのが目的。
- ATDD:受け入れ条件を実装前に合意し、それをテストとして先に書く。作るもののズレを着手前に潰す。
- CI 自動実行:コミットやマージのたびにテスト群を自動で回し、退行を早期に検出する。
- シフトレフト:テストや品質活動を工程の早い段階へ前倒しし、欠陥を安く直せるうちに見つける。
- シフトライト(本番テスト):本番環境での監視や検証へ右に寄せ、テスト環境では再現しない問題を捉える。
- カナリアリリース:新版を一部のトラフィックにだけ流し、異常を検知したら全面展開前に止める。
- ブルーグリーン/A-B:稼働系と待機系を切り替えて即時ロールバックを可能にする(ブルーグリーン)、または2案を比較する(A/B)。
- フィーチャーフラグ段階検証:機能をフラグで包み、対象を絞って有効化しながら影響を確認する。
- 合成監視(Synthetic Monitoring):本番に対し定期的に擬似リクエストを投げ、利用者が踏む前に劣化や停止を検知する。
- ミューテーション/カバレッジを CI ゲート:変異検出率やカバレッジに下限を設け、満たさない変更をマージさせない。テストの実効性を担保する。技法そのもの(変異の作り方・実行・スコア解釈)は
mutation-testing.md が扱う。ここはその戦術面、すなわち求めたスコアを CI のゲートに組み込んでマージを止める運用だけを扱う。
- テストデータ管理:fixture、factory、seeding でテストデータを再現可能かつ独立に用意し、テスト間の汚染を防ぐ。
- フレークテスト対策:不安定に失敗するテストを検出し、隔離、再試行、原因修正で信頼性を保つ。緑が信用される状態を維持するのが目的。
TypeScript example: BDD
Gherkin の feature と、それを満たす cucumber-js のステップ定義を最小で対にする。
# features/discount.feature
Feature: 割引適用
Scenario: 通常会員に 10% 割引
Given 価格が 1000 円の商品
When 割引率 0.1 を適用する
Then 支払額は 900 円になる
import { Given, When, Then } from "@cucumber/cucumber";
import assert from "node:assert/strict";
let price: number;
let total: number;
Given("価格が {int} 円の商品", (p: number) => { price = p; });
When("割引率 {float} を適用する", (rate: number) => {
total = Math.round(price * (1 - rate));
});
Then("支払額は {int} 円になる", (expected: number) => {
assert.equal(total, expected);
});
群Bの網羅の扱い
これらは網羅技法でなく「どこにテストを差し込むか」の戦術であり、網羅率は定義しない。
代わりに「網羅を担保する仕掛け」として読む。
ミューテーションとカバレッジを CI ゲートにして網羅の下限を強制する。
CI 自動実行で回帰の網羅を継続させる。
テストデータ管理で網羅の再現性を保つ。
フレーク対策で網羅の信頼を保つ。
BDD/ATDD は受け入れ基準を網羅対象として実装前に固定し、それを Gherkin で1対1に橋渡しする。
群C: 静的テスト
コードや成果物を実行せずに欠陥を見つける。
ISO/IEC/IEEE 29119 でも一級のテスト活動として扱われ、動的テストより早く安く欠陥を捕まえられる。
- コードレビュー/ピアレビュー:他者が差分を読み、欠陥、設計の歪み、過剰設計を指摘する。このリポジトリでは code-reviewer agent(正しさ中心)と ponytail-review(過剰設計のみ)を使い分ける。
- 静的解析(Linter/型チェッカ):ESLint や tsc で、実行前にバグの芽(未使用、型不整合、危険なパターン)を機械的に検出する。
- インスペクション/ウォークスルー:前者は役割を定めた形式的な欠陥検出会、後者は作成者が成果物を説明しながら意見を募る非形式な検討。
- 仕様設計レビュー:コードになる前の要件や設計を検証し、下流に流れると高くつく欠陥を上流で止める。
- 複雑度メトリクス検査:循環的複雑度や認知的複雑度を測り、閾値超過の箇所を保守困難の予兆として洗い出す。
上のうちレビュー系・メトリクス系は運用の話で概要で足りるが、静的解析(Linter/型チェッカ)だけは「どのルールを選び、どう全適用し、違反0をどう固定するか」という具体手順がある実技法なので、以下で展開する。
静的解析(Linter/型チェッカ)
遂行手順(着手→完了)
静的解析の本体は「機械的に検出できる欠陥クラスを、ルールセットの選定→全適用→違反0の固定で取り切る」ことだ。次の順で進める。
- ルールセットを選定する:使う言語・フレームワークの推奨セット(ESLint の
recommended、@typescript-eslint 推奨、tsc の strict)を基点に、プロジェクト固有の危険パターン(any 禁止、floating-promise 検出、未使用 export)を足す。何を検出対象にするかをここで確定する。
- 検出対象クラスを把握する:選んだルールが何を捕まえるか(未使用、型不整合、到達不能、安全でない型変換、未処理の Promise)を一覧にする。逆にカバーしないクラス(振る舞いのバグ)を明示し、動的テストへ回すと決める。
- 全ファイルに適用する:除外設定(
ignorePatterns、exclude)に穴がないか確認し、生成物以外の全ソースへ走らせる。一部ディレクトリだけ走って通っている「見かけの緑」を潰す。
- 違反を0にする:検出された違反を修正する。直せない箇所だけ行単位の抑制(
eslint-disable-next-line + 理由コメント)に留め、ファイル単位やルール単位の無効化で逃げない。抑制の総数を把握しておく。
- CI で違反0を固定する:Linter と型チェックを CI のゲートにし、新規違反が入ったらマージを止める(群Bの CI ゲート戦術と接続)。これで違反0が一度きりでなく継続する。
導入のタイミングは最初のコミットと同時が原則だ。新規プロジェクトで「コードが溜まってから整える」と、導入時に全違反の一括清算(連続修正コミットの山)が発生し、清算までの間に書かれたコードは無検査で積み上がる。初日にゲートを立てれば違反はコミット単位に分散し、清算コストは発生しない。既存プロジェクトへの後付けだけが一括清算を正当化する。
完了チェック(もれ確認)
- 推奨セットが全部有効か:
recommended/strict を部分的に切っていないか。切ったルールがあれば、その分の欠陥クラスが素通りする。
- 除外に穴が無いか:
ignore/exclude で意図せず実ソースが解析対象外になっていないか。除外パターンが広すぎると未解析ファイルが残る(ルールの抜け検出)。
- 抑制が行単位かつ理由付きか:
disable がファイル/ルール単位で乱用されていないか、各抑制に理由コメントがあるか。理由なき抑制は将来の欠陥を隠す。
- CI ゲートが効いているか:ローカルだけでなく CI で違反0が強制され、新規違反でマージが止まるか。手元実行のみだと違反0が揮発する。
- 動的テストとの境界が明示されているか:静的解析が捕まえない振る舞いのバグを、動的網羅へ回すと決めたか(手順2)。静的の緑を振る舞いの網羅と混同しない。
群Cの網羅の定義
静的テストの網羅は動的網羅とは別軸で定義する。
網羅基準: レビューはチェックリスト項目の踏破、静的解析は有効ルールセットの全適用 + 違反0、複雑度メトリクスはしきい値超過0で網羅とみなす。
網羅手順:
- レビュー観点チェックリストを定義し全項目を確認する。
- linter と型チェッカの推奨ルールを有効化し全ファイルに適用する。
- 複雑度しきい値を決め超過箇所を洗う。
達成チェック: 静的網羅は動的テストでしか測れない振る舞いの網羅と混同しない。
静的は早く安いが別の層であり、振る舞いの網羅を置き換えない。
群D: その他の観点
上の群に収まりにくいが現場で頻出する観点。
- 手動テスト:人が操作して確認する。自動化が割に合わない探索的検証や、初見の使い勝手の評価に向く。
- アルファ/ベータテスト:アルファは社内、ベータは限定された実利用者に先行公開し、本番前のフィードバックを得る。
- ドッグフーディング:開発チーム自身が日常的に自製品を使い、利用者目線の問題を早期に体感する。
- ネガティブテスト:不正な入力や想定外の操作を与え、適切に拒否、エラー処理されるかを確認する。
- ポジティブテスト:仕様どおりの正しい入力で、期待どおり動く(ハッピーパス)ことを確認する。
- 境界外/極値テスト:許容範囲の外側や最大最小の極値を与え、境界の処理が破綻しないか検証する。
- 冪等性テスト:同じ操作を複数回適用しても状態が一度きりと同じになることを確認する。リトライや再送の安全性を担保する。
- 並行性/レースコンディションテスト:複数の処理を同時に走らせ、競合による不整合が起きないか確認する。
- タイムアウト/リトライ/サーキットブレーカ挙動:遅延や失敗を注入し、打ち切り、再試行、遮断が設計どおり働くか検証する。
並行性とレースコンディションは、テストでは全 interleaving を踏みきれない点に注意する。
実行のたびにスケジューリングが変わり、たまたま緑になった結果は不具合の不在を保証しない。
順序と途中状態の正しさを設計レベルで保証したいなら、このリポジトリの loop-engineering(TLA+)でモデル検査し、得た反例をテストへ落とす。
テストは設計検証を置き換えるものではなく、補完するものとして使う。
上のリストのうち、運用形態(手動、アルファ/ベータ、ドッグフーディング)は読んで分かる戦術なので概要止まりにする。
一方、冪等性、並行性/レースコンディション、境界外極値/ネガティブは具体的な手順がある実技法なので、以下で着手から完了まで展開する。
冪等性テスト
遂行手順(着手→完了)
冪等性テストの本体は「同じ操作を2回以上適用した後の状態が、1回適用後と一致するか」を確かめることだ。次の順で進める。
- 冪等であるべき操作を列挙する:リトライ、メッセージ再送、Webhook 再配信、ユーザーの二重クリックなど、同じ要求が複数回届きうる操作を全部書き出す。「1回しか来ない前提」の操作は対象外だが、ネットワーク越しの操作はまず複数回来ると見なす。
- 同一性の判定基準を決める:何をもって「1回適用と同じ状態」とするかを定める。永続化された行、残高、在庫数、外部への送信回数など、観察対象を1つ確定する(複数あれば操作ごとに分ける)。
- 冪等キーの有無を確認する:操作が冪等キー(リクエスト ID、操作 ID)で重複を弾く設計なら、同じキーで2回、違うキーで2回の両方を試す。同じキーは1回分、違うキーは2回分になるのが正しい。
- 2回適用と1回適用を比較する:1回適用後の状態を取り、続けてもう一度(あるいは N 回)適用して、状態が一致することをアサートする。
expect(twice).toEqual(once) のように最終状態で比較する。
- 副作用の回数も検証する:状態が同じでも、外部への送信やメール発火が2回起きていないかをモックの呼び出し回数で確かめる。状態は冪等でも副作用が漏れる設計は多い。
完了チェック(もれ確認)
- 列挙した全リトライ操作に冪等テストがあるか:手順1で挙げた「複数回来うる操作」の数と、冪等テストの数が一致する。差があれば未確認の操作にリトライ穴が残る。
- 状態と副作用の両方を見たか:最終状態の一致(手順4)だけでなく、外部副作用の回数(手順5)も検証したか。状態だけだと二重送信を見逃す。
- 冪等キーの分岐を両方踏んだか:同じキー(1回分)と違うキー(2回分)の両方をテストしたか。片方だけだとキー判定のバグがすり抜ける。
- 3回以上でも不変か:2回で止めず N 回(例 3 回)でも状態が動かないことを確認したか。2回だけ通って3回目で崩れる実装がある。
TypeScript example: 冪等性
同じ操作を2回適用し、状態が1回適用後と一致することを確認する。
import { describe, it, expect } from "vitest";
function reserve(stock: { reserved: boolean; count: number }) {
if (stock.reserved) return stock;
return { reserved: true, count: stock.count - 1 };
}
describe("reserve は冪等", () => {
it("2 回適用しても 1 回分しか減らない", () => {
const once = reserve({ reserved: false, count: 10 });
const twice = reserve(once);
expect(twice).toEqual(once);
expect(twice.count).toBe(9);
});
});
並行性/レースコンディションテスト
遂行手順(着手→完了)
このテストの本体は「同時に走ると壊れる不変条件」を炙り出すことだ。ただしテストでは全 interleaving を踏みきれない(前述)ため、テストで踏めるのは代表的な競合だけで、網羅は設計検証に委ねる。次の順で進める。
- 共有状態と競合操作を列挙する:複数の実行主体が同時に触る共有資源(残高、在庫、カウンタ、フラグ、キャッシュ)と、それを read-modify-write する操作を全部書き出す。共有状態が無ければ競合は起きないので対象外。
- 守るべき不変条件を1つ書き下す:「残高は非負」「在庫は予約数を超えて減らない」「カウンタの最終値は加算回数と一致」など、並行実行後も成り立つべき述語を明文化する。これが破れたかどうかでテストの合否を決める。
- 競合操作を並行起動する:同じ資源を叩く操作を
Promise.all などで同時に発火させる。可能なら回数を増やし(例 100 並列)、たまたま直列化された緑を避ける。
- 不変条件の破れを検証する:並行実行後の最終状態が手順2の述語を満たすことをアサートする。例えば「100 回の加算後にカウンタが 100 か」。直列なら 100 だが、ロック漏れがあると 100 未満になる。
- 直列化の証拠を残す:正しい実装ならロックや楽観ロック(バージョン番号、CAS)で直列化されているはずなので、それが効いていることを最終状態で確かめる。失敗時にどの interleaving で壊れたかはテストでは特定しにくい点を受け入れる。
- 網羅は設計検証へ送る:全 interleaving・順序・途中状態の正しさは、このリポジトリの loop-engineering(TLA+)でモデル検査する。TLC が出した反例トレースを Gherkin 経由でここのテストへ落とし、テストは「設計で見つけた具体的な壊れ方の回帰」として使う。テスト単独で並行正しさを保証したと主張しない。
完了チェック(もれ確認)
- 不変条件が明文化されているか:手順2の述語が1つ以上書かれ、テストのアサートと1対1で対応しているか。述語が曖昧だと「たまたま緑」を合格と誤認する。
- 並列度が十分か:2並列で止めず、競合が顕在化する程度(数十〜数百)まで上げたか。低い並列度はレースを踏まない。
- 全 interleaving を踏んだと誤認していないか:テストの緑を「並行正しさの証明」と書いていないか。網羅は TLA+ 側にあることを明記したか(手順6)。
- 設計検証の反例が回帰テスト化されているか:loop-engineering で見つけた反例トレースが、ここの並行テストとして固定されているか。反例が再発を防ぐテストになっていないと、設計検証の成果が揮発する。
境界外/極値テスト、ネガティブテスト
遂行手順(着手→完了)
この2つは「異常な入力に対して破綻せず正しく拒否するか」を系統的に確かめる。場当たりに変な値を入れるのではなく、同値分割の無効クラスと境界値分析から異常入力を機械的に導く。次の順で進める。
- 入力の有効範囲を確定する:各引数の仕様上の有効範囲(型、最小最大、長さ、形式、必須/任意)を書き出す。これが無いと「異常」を定義できない。
- 無効同値クラスを列挙する:有効範囲の外側を同値分割で分ける。範囲下限未満、上限超過、型違い(数値に文字列)、
null/undefined/空文字/空配列、形式違反(不正なメール、壊れた JSON)など、無効クラスを網羅的に挙げる。詳細な分割手法は同値分割の reference に従う。
- 境界の極値を取る:有効範囲の境界の内外(最小-1、最小、最大、最大+1)と、表現上の極値(
0、負数、Number.MAX_SAFE_INTEGER、空、超長文字列)を境界値分析で取る。off-by-one はここで捕まる。
- 各異常入力の期待挙動を決める:拒否なのか、既定値へのフォールバックなのか、特定の例外/エラーコードなのかを入力ごとに確定する。「なんらかのエラー」ではなく、どのエラーが出るべきかまで決める。
- 拒否のされ方を検証する:異常入力を与え、期待した例外型・エラーメッセージ・ステータスで弾かれることをアサートする。握り潰して正常系に紛れ込ませていないか、状態を汚していないか(部分的に処理して途中で落ちていないか)も見る。
完了チェック(もれ確認)
- 全無効クラスに1ケース以上あるか:手順2で挙げた無効同値クラスの数と、ネガティブテストの数が対応しているか。挙げたが書いていないクラスは穴。
- 境界の内外を両方踏んだか:各境界で「有効側の端」と「無効側の端」(最小と最小-1 など)の両方を取ったか。片側だけだと off-by-one を見逃す。
null/空/型違いを入れたか:null、undefined、空文字、空配列、型違いという定番の異常を各引数に対して試したか。これらは最も漏れやすく最も多くバグを出す。
- 期待エラーが具体的か:「エラーが出る」で済ませず、例外型やコードまで固定したか。曖昧なアサートは別の理由で落ちても緑を装い、誤った拒否を見逃す。
- 拒否時に状態が汚れていないか:異常入力で途中まで処理が進み、永続状態や共有状態を部分的に書き換えていないか(冪等性・並行性の崩れに繋がる)。
群Dの網羅の定義
この群は観点ごとに型が分かれる。
ネガティブ、ポジティブ、境界外極値、冪等性は A 型で網羅できる。
正常入力集合(ポジティブ)と不正入力集合および極値入力集合(ネガティブ、境界外)を同値分割と境界値で出し切り、すべて覆う。
冪等性は対象操作を2回以上適用して1回適用と一致することを、全リトライ対象操作で確認する。
並行性、レースコンディション、タイムアウト、リトライ、サーキットブレーカは網羅困難である。
テストでは全 interleaving を踏みきれず、緑は不具合の不在を保証しない。
停止規範: 代表的な競合シナリオと障害注入(遅延、失敗)で、設計どおりの打ち切り、再試行、遮断を確認する。
これらの網羅は loop-engineering(TLA+)でモデル検査し、反例をテストへ落とすことで担保する。
手動、アルファ/ベータ、ドッグフーディングは B 型で、網羅率を定義しない。
観点リスト、期間、被験者数で停止し、見つけた欠陥は体系的技法へ昇格させる。
非機能、品質特性のテストは別ファイルへ
品質特性(ISO/IEC 25010)に沿う非機能テストは、測定系(性能、負荷、ストレス、スパイク、ソーク、スケーラビリティ、キャパシティ)を nonfunctional-perf.md に、品質保証系(セキュリティ、a11y、互換、i18n、信頼性、可用性、カオス、耐障害性、コントラクト、データ品質マイグレーション)を nonfunctional-attributes.md にまとめた。