writing-tests について
PostHog の開発向けに、追加するテストと配置場所を判断するスキルです。具体的な回帰の検出、既存テストの拡張、動作を検証できる低コストなテストレベルを重視します。
主な機能
- 01実際の回帰を検出するテストか評価
- 02関連する既存テストの拡張を優先
- 03冗長なテストや実装依存のテストを回避
- 04低コストなテストレベルを選択
- 05Python とフロントエンドのテスト指針
活用シーン
- 01カバレッジ追加前にテストの価値を評価
- 02既存テストに動作のバリエーションを追加
- 03ユニット、データベース、ブラウザテストを選択
- 04不要なデータベース準備を避けて入力検証をテスト
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ posthog/ posthog --skill writing-testsインストール後、Claude Code で/writing-testsを入力して読み込まれたか確認します。
同梱ファイルreferences/database-free-test-classes.mdreferences/mistakes-we-make.md
詳細
残す価値のあるテストを書く
その根拠と、人間向けにまとめた同じルールはハンドブックにあります: バックエンドコーディング規約 › テスト(docs/published/handbook/engineering/conventions/backend-coding.md)。
このスキルは運用上のゲートです — テストを書く前に実行してください。判断手順に加えて、私たちが実際に出してしまうバグの形状のカタログを携えています。
このスキルが対象とするのは追加されるカバレッジであり、新しいテスト関数ではありません。既存のブロックに追加する 1 つのフィクスチャと 1 つのアサーションも、同じ 2 つの問いを通ります。なぜなら、それがこのスキルが推奨する形だからです。
ゲート: 2 つの問い
テストを書く前に、どちらもそれぞれ一文で答えてください。
1. そのテストは居場所を得ているか?
このテストは、既存のどのテストも捉えていない、どのような現実的なリグレッションを捉えるのか?
具体的に答えられないなら — バグ、コードパス、壊れる入力の名前を挙げられないなら — そのテストは書かないでください。 「カバレッジのため」「良いプラクティスだから」「その関数が存在するから」は答えになりません。
良い答えはこういうものです: 「誰かが parse_filters から team_id 句を落としたら、これは失敗する」 あるいは 「空のコホート入力を渡すと以前は 500 になったが、これは 400 を固定する」。
それが残す価値のあるテストです。
各テストは、仮定の話ではなく、私たちが実際に踏んだ障害モードを狙ってください。 PostHog が出してリバートするバグは、いくつかの形状に集約されます — それぞれを捉えるテスト、そしてユニットテストが捉えるべきではない障害モードとともに、references/mistakes-we-make.md にカタログ化されています。 あなたのテストがそのどれにも当てはまらないなら、残す価値があるか懐疑的になってください。
2. それはどこに置くか?
最初の問いを通過すると得られるのは カバレッジ であり、新しいテスト関数ではありません。 最も近い振る舞いをすでにカバーしているテストを見つけて、次に答えてください:
なぜこれをそのテストの 1 ケースにできないのか?
あなたのケースがその振る舞いのバリエーションであるなら、それを拡張してください: Python なら @parameterized のケース、Jest なら test.each の行です。
独立した新しいテストを書くのは、拡張がうまくいかないときであり、その理由を一文で言えるはずです: セットアップが異なる、その振る舞いが別のユニットに属する、あるいは関連するテストが存在しない。
「独立したテストのほうがきれいだから」はそれだけでは理由になりません。その 3 つのどれに当てはまるかを挙げてください。
拡張するのは重複をなくすためであり、セットアップ時間を節約するためではありません。
パラメーター化されたケースもそれ自体が 1 つのテスト呼び出しなので、setUp と beforeEach は独立した関数に対するのとまったく同じように実行されます。
これは限界も定めます: 同じ振る舞いのバリエーションは畳み込み、無関係な振る舞いに関するアサーションを、すでにパスしているテストに付け足さないでください。
書く前に検索してください。 最も近い既存のテストを探していないなら、この問いには答えられません。
書かないこと — 5 つの「しない」
価値の低いテストのほとんどは、次のいずれかに当てはまります。それを見分け、スキップしてください。
-
些末なテスト / フレームワークの動作。 ゲッター、セッター、定数、dataclass のフィールド代入、Django が行を保存したこと、DRF がフィールドをシリアライズしたこと、あるいはライブラリがドキュメントどおりに動作することをテストしないでください。 それは自分のコードではなく、他人のコードをテストしています。
-
変更検知テスト。 実装をそのまま写しただけのテスト — どのプライベートメソッドが、どの順序で呼ばれたかを、現在のコードに合わせてモックを配線してアサートするテスト — は、リファクタリングのたびに失敗し、実際のバグを何も捉えません。 それを生み出す段取りではなく、公開インターフェースを通した観測可能な振る舞い(戻り値、永続化された状態、発行されたイベント、HTTP レスポンス)をテストしてください。 Change-Detector Tests Considered Harmful と Prefer Testing Public APIs を参照してください。
-
冗長なカバレッジ。 同じ経路を異なるデータで実行する 10 個のテストは、1 個のパラメータ化されたテストです。 新しいテストが既存のテストのバリエーションであるなら、それは
@parameterizedケース(Python)またはtest.eachの行(Jest)であり、新しいテスト関数ではありません。 AI が生成したテストスイートはまずここで肥大化します。数百のほぼ同一のケースが、30 秒のスイートを 3 分に変えてしまいます。 -
カバレッジ稼ぎ。 数値を達成するためにテストを追加しないでください。カバーされていない行は情報であり、欠陥ではありません。 ある分岐をテストする唯一の理由がカバレッジレポートであるなら、その分岐にテストはおそらく不要です — あるいはそのコードはデッドコードであり、テストするのではなく削除すべきです。
-
言語をまたいだソースのスクレイピング。 ある言語のテストから別の言語のソースを読んだり正規表現で解析したりしないでください — Python のテストが
Path(...).read_text()で.tsファイルを読み、category: '...'を照合する、TS のテストが.pyファイルを grep する、といった類です。 これは、振る舞いを何も変えない編集(リネーム、再フォーマット、ファイルの移動、コメント)で壊れる脆い文字列マッチを通じて 2 つのツリーを結合し、ランタイムについて何も証明しません — テスト内で両者が実際に一緒に実行されることは決してないからです。 2 つの側が本当にある値の集合について一致しなければならないなら、単一の信頼できる情報源 — 生成された成果物、または両者がインポートするチェックイン済みのデータファイル — を用意し、それに対してアサートしてください。そうでなければ、ずれは 2 つの側が本当に出会う場所(API コントラクトテスト、レンダリングされた出力、ラウンドトリップ)で表面化させるべきであり、相手言語のソースから文字列をスクレイピングして明らかにするべきではありません。
答えが「書かないこと」であるとき、正しい行動はしばしば 既存のテストを拡張する(パラメータ化されたケースを追加する)か、テストするのではなく コードを削除する ことです。 どちらもスイートの表面積を縮小します。
書くなら — ピラミッドの下へ重みを置く
その地位にふさわしいテストであっても、安価でなければなりません。 コストは梯子のようなもので、各段はその下の段よりおおよそ一桁遅く、不安定になります:
pure function / unit → kea logic test → Django TestCase → ClickHouse-backed test → Playwright e2e
cheapest most expensive
目標は比率であり、上限ではありません。下の段にたくさん、上の段にごくわずかです。 千個の純粋なユニットテストは、Django を起動する十個のテストよりも安価で信頼性が高く、それは Playwright の一回の実行よりも安価です。 カバレッジを増やしたいなら、下の段で追加してください。
何かを安価にテストするのが難しいとき、それは設計のシグナルです — 引き出しましょう。エスカレートしてはいけません。 ロジックを動かす唯一の方法がデータベース、リクエスト、レンダリングを立ち上げることなら、そのロジックは依存関係と絡み合っています。 それを純粋な関数または kea logic に引き出し、_それ_を直接テストしてください。より高速なテストと、より良い因数分解されたコードが同時に得られます。 次の段へエスカレートすることは最後の手段であり、デフォルトではありません。
- ロジックが純粋な関数にある(または移動できる)なら、その関数をテストしてください — DB、リクエスト、レンダリングを立ち上げないでください。
- フロントエンド: 振る舞いがロジックにある場合は常に、完全なコンポーネントのレンダリングではなく、kea logic (
logic.actions/logic.values) をテストしてください。render()+ DOM クエリのテストは、DOM だけが示せるもののためのものです。 - ClickHouse やブラウザに頼るのは、そのリグレッションが本当にそこに存在するときだけです — テストを思いついた最初の方法だったからではありません。
Python (pytest / Django)
-
TransactionTestCaseではなくTestCaseを使用してください。どうしても必要な場合を除いて。TransactionTestCaseはテスト間でトランザクションをロールバックする代わりに DB をフラッシュします。劇的に遅くなり、テスト間の干渉のよくある原因となります。 これは Postgres の分離レベルの選択であり、どのデータストアに触れるかとは直交します。ClickHouse をバックエンドにしたテストでも単なるTestCaseです(ClickhouseTestMixinが ClickHouse をセットアップします)。したがって、ClickHouse に到達することは切り替える理由にはなりません。 人々がTransactionTestCaseに頼って解決しようとする一般的なケースには、通常より安価な代替手段があります:transaction.on_commitの副作用をテストする →TestCase+self.captureOnCommitCallbacks(execute=True)を使用してください。- 実際の別スレッドから見える接続が必要(
thread_sensitive)→asyncio.run(...)ではなくasync_to_sync(...)を使用してください。TransactionTestCaseは、そのリグレッションがTestCaseでは隠されてしまうコミット済みのトランザクション境界を本当に必要とする場合にのみ使用してください。
-
テストクラスは、使用するデータベースのみを受け取ります。
BaseTestとAPIBaseTestは Django のTestCaseを継承しているため、どちらかを継承したクラスは実行にデータベースが必要です。setUpTestDataが組織、プロジェクト、チーム、ユーザーを書き込み、すべてのテストメソッドはトランザクション内で実行されます。純粋なアサーションだけのクラスは、そのすべてのコストを払いながら、1 行も読み取りません。そのようなケースはdjango.test.SimpleTestCaseに置いてください。これはデータベースアクセスを拒否するため、データベースが不要であったことを証明できます。posthog/test/repo_invariants/test_database_free_test_classes.pyのラチェットがこれを強制します。ここには既に存在するクラスの凍結されたリストが保持されており、新たに追加するプルリクエストを失敗させます。これが引っかかった場合、references/database-free-test-classes.md に、失敗の意味、それに応える 3 つの方法、ベースラインを再生成する方法が記載されています。 -
専用のデータマイグレーションテストは一時的なものです。 サポートされているすべての環境がマイグレーションを適用し、ロールバック期間が終了し、サポートされているアップグレードが古いデータ状態に依存しなくなった後は、専用のテストを削除してください。期限切れのテストはスキップとしてマークするのではなく削除してください。マイグレーションファイルと、マイグレーションツール、安全性チェック、再利用可能なバックフィルシステム、およびまだ実行可能なバックフィルのためのテストは保持してください。マイグレーションの安全性ワークフローには
/django-migrationsを使用してください。 -
DRF の入力バリデーションは
APIBaseTestのラウンドトリップではなくSimpleTestCaseに属します。 エンドポイントに不正な形式のボディを POST し、400 をアサートするテストは、APIBaseTestが Postgres に Organization + Team + User を構築し、テストをトランザクションでラップするコストを払います。それは完全にメモリ内で実行されるバリデーションを試すためだけです。 DRF のフィールドバリデータ(required、型変換、choices、min/max、正規表現)とvalidate_<field>メソッドは、データベースもリクエストもなしでSerializer(data=...).is_valid()内で実行されます。フィールドレベルのバリデーションはto_internal_valueで行われ、通常self.contextを必要とするオブジェクトレベルのvalidate()の_前に_実行されます。したがって、不正なフィールドのケースは DB に触れるコードに決して到達しません。 シリアライザを直接テストし、.errorsをアサートしてください:class TestTeamValidation(SimpleTestCase): # no DB — not APIBaseTest def test_sample_rate_too_many_digits(self) -> None: s = TeamSerializer(data={"session_recording_sample_rate": "30001"}, partial=True) assert not s.is_valid() assert s.errors["session_recording_sample_rate"][0].code == "max_digits"ケースマトリックスをシリアライザーに落とし込む際は、DB に依存するエンドポイントテストを 1 つ配線ガードとして維持(または追加)してください — つまり、ビューセットが実際にこのシリアライザーを呼び出し、不正なリクエストが 400 で拒否されることを確認するものです。DB を使わないシリアライザーテストはバリデーションロジックを証明しますが、ビューセットがそのシリアライザーに配線されていることは証明し_ません_(
serializer_classを削除したり、is_valid()をスキップしたり、is_valid(raise_exception=True)の呼び出しを止めたりするリファクタリングは、すべてのSimpleTestCaseを通過しつつ、壊れたエンドポイントを出荷してしまいます)。エンドポイントのケースを 1 つ入れればそのギャップは埋まり、マトリックスはSimpleTestCaseに残ります。インラインでインスタンス化されるクエリシリアライザー(例:Serializer(data=request.query_params).is_valid(raise_exception=True))の場合、配線ガードは不正なクエリパラメータ → 400 のアサーションになります。 さらに 2 つの注意点があります。第一に、.errorsは DRF の_生の_コード(invalid、max_digits)を保持します。{"attr", "code", "detail", "type"}という HTTP エンベロープは後からexceptions-hogによってレンダリングされます(invalid→invalid_inputにマッピングされます) — このレンダリングはフレームワークの挙動なので、ケースごとに再アサートしないでください(配線ガードのテストがエンベロープを 1 回カバーします)。第二に、本当に DB を必要とするバリデーションはエンドポイントに残します — 一意性チェック、PrimaryKeyRelatedFieldのクエリセット参照、関連オブジェクトの存在、権限/チームのスコープ、パスワードハッシュのチェックなどです。これらをSimpleTestCaseに押し込まないでください。 -
パラメータ化には
parameterizedライブラリを使って繰り返しのアサーションを行ってください — テスト本体をコピー&ペーストしないでください。 -
テスト実行前にケースが判明している場合は
parameterizedのケースを、判明していない場合のみself.subTestを使ってください。たとえば、クエリが返す行ごとに 1 つのチェックを行う場合などです。 私たちの pytest 設定ではself.subTestがフェイルファストするため、CI がテストをリトライできます。 pytest のsubtestsフィクスチャは使わないでください。リトライが成功した後も junit ファイルに失敗が残ってしまいます。 -
Python テストではドキュメントコメントを書かないでください(ハウスルール)。
-
モックするのは真の境界のみ — ネットワーク、外部 API、時計、キューです。 自分自身の内部ヘルパーをモックしないでください(それがチェンジディテクター型テストの生まれ方です)。
-
人物/グループ/コホートデータ:
posthog/test/persons.pyのヘルパー(create_person、create_group、create_group_type_mapping、add_cohort_membersなど)を使ってください —Person.objects.create()などの ORM 呼び出しを直接使わないでください。 完全な API リファレンスと根拠についてはposthog/test/AGENTS.mdを参照してください。 -
リポジトリ全体のガードは
posthog/test/repo_invariants/に置きます。 テストの入力がリポジトリ全体である場合 —apps.get_models()を走査したり、コールドなdjango.setup()後にsys.modulesを検査したり、すべてのルートやシグナルレシーバーを列挙したり、ツリーをベースラインに対してrglobしたりする場合 — どこかのファイルがそれを壊す可能性があり、差分ベースのテスト選択ではそれを選択できません。このディレクトリはすべてのバックエンド PR(プロダクト限定やドラフトも含む)でrepo-checksCI ジョブにおいて Postgres/ClickHouse なしで無条件に実行され、Core シャードはそれをスキップします。入力が限定されているテストは、対象とするコードのそばに置きます。
フロントエンド (Jest)
- ファイルごとにトップレベルの
describeブロックを 1 つにします(ハウスルール)。 - コンポーネントのレンダリングよりもロジックのテストを優先します(上記のはしごを参照)。
- テスト本体をコピーするのではなく、バリエーションには
test.eachを使用します。 - 大きな DOM をレンダリングするコンポーネント(カレンダー、分類フィルター、シーン全体)では
*ByRole(..., { name: ... })を避けてください。 ロールクエリはすべての要素を走査してgetComputedStyleベースのチェックとアクセシブル名の計算を実行します。jsdom ではクエリごとに秒単位かかり、それがwaitFor/findBy*のリトライごとに掛け算されます。このパターンにより、CI で p95 が 40 秒になる単一テストが発生したことがあります。getByText、getByTestId/data-attrセレクター、またはgetByLabelTextを優先してください。ロールクエリがどうしても必要な場合はwithin(<小さなコンテナ>)でスコープを限定します。jest-no-byrole-name-queriessemgrep ルールがこれを検出します。 - 巨大なスナップショットは禁止です。 大きなレンダリングツリーやシリアライズされた blob に対するスナップショットは、変更検出用のテストであり、リポジトリを肥大化させ、無関係な変更のたびに壊れます。 小さく意図的な値をスナップショットするか、代わりに特定のフィールドをアサートしてください。
常に — 決定性と分離
- 状態遷移と競争するのではなく、制御してください。 テスト対象の動作に合った制御を選びます:
- 経過時間の動作には、作業を開始する前にフェイクタイマーを有効にし、その動作が要求する時間だけ正確に進めます。
- 最終的な非同期結果には、観測可能な結果に対して
wait_for/waitForを使用します。 - 実行中の状態には、モックした境界がテストで制御された promise を待つようにします。保留中の状態をアサートし、promise を解放してから、最終的な状態をアサートします。デバウンス、
setTimeout、kea ブレークポイント、または高速なモックが次の行でもまだ保留中であると仮定しないでください。テストのセットアップは、本番の動作を変えずに内部の遅延を短縮する場合があります。
time.sleep/ 任意の待機は使わないでください。 sleep はいつか起きるフレークであり、毎回の実行を遅くします。上記の対応する制御またはtime_machine.travelに置き換えてください。- テスト内の絶対日付は、クロックを固定するまで時限爆弾です。
フィクスチャの日付は、実クロックが置いた場所にとどまっている間だけ意味を保ちます。テスト対象の何かがその日付を
nowと比較して測定する場合 — 経過時間、ウィンドウ、"recent" フラグ、有効期限 — アサーションは今日は成立し、数週間後に、すべてのオープンブランチで一斉に失敗します。 日付をアプリケーションの状態に固定することは、クロックを固定することではありません。"evaluated at" の値を固定の瞬間に設定し、実時刻はそのままにするテストは、実時間がウィンドウを超えてずれると依然として失敗します。コードがnowを再読み取りし、両者が一致しなくなるからです。 プロセスのクロックをフィクスチャが語る瞬間に固定してください — Python ではtime_machine.travel(..., tick=False)、Jest ではjest.useFakeTimers()とjest.setSystemTime()を組み合わせ、finallyでjest.useRealTimers()により解放します — あるいはフィクスチャをnowからの相対 (now - 2 days) で書き、距離こそがテストの主張する内容になるようにします。 クロックの固定は、プロセス内でそれを読み取るものだけをカバーします。次のルールが残りをカバーします。 これを、明示的に凍結したクロックだけでなく、テスト内のすべての絶対日付に対して問いかけてください: 今日がその日付の一年後であるとき、これは何をアサートするのか? 答えが「同じこと」でないなら、コミットする前に修正してください。 - 凍結したクロックはインフラを凍結しません。
time_machine.travelはプロセス内のクロックにパッチを当てますが、その外側のすべては依然として実クロックで動作します — ClickHouse の TTL、Postgres のnow()デフォルト、S3 のライフサイクルルール、別のサービスのトークン有効期限チェック。 したがって、絶対日付に凍結してから、何かが経過時間で判定する行を書き込むと、時限爆弾が仕込まれます: 数週間は緑、その後、実時刻が保持ウィンドウを超えてずれると永久に赤。 これはすべてのブランチで一斉に失敗するため、目の前にあるどの PR が原因に見えます — そして、時間が奪われるのは修正そのものではなく、この誤った原因帰属です。 ほとんどの ClickHouse テーブルはすでに安全です。TTL をttl_period()(posthog/clickhouse/kafka_engine.py) を通して構築し、これはsettings.TESTの下で""を返すため、テストでは TTL がまったくありません。TTL句をハードコードしているテーブルは、このガードから抜け出します —ai_eventsがその一つです。仮定せずに確認してください:grep -rlE '^\s*TTL ' posthog/models/ posthog/clickhouse/ --include=*.py。 代わりに、行の寿命を周囲のクロックから独立させてください — 挿入時に保持カラムを固定し、bulk_create_ai_eventsがretention_days=10000を書き込むようにします。凍結した日付を先に進めても、タイマーがリセットされるだけです。 - 連続する 2 回の書き込みは時間的に順序付けられていません。
一方の書き込みの記録されたタイムスタンプからカットオフを導出し、次の書き込みがその後に来ると期待しないでください。
クロックは見た目よりも粗いのです: ライブラリのタイムスタンプはミリ秒で、Linux ではファイルの mtime はカーネルの粗いクロックに由来し、これはスケジューラのティック分だけプロセス内クロックより遅れます。
ライブラリは、あなたがサンプリングしたものとは別のクロックを読み取る場合もあります — delta-rs のタイムトラベルは、
DeltaTable.history()が報告するtimestampではなく、_delta_logコミットファイルの mtime に対して日時を解決します。 そのようなテストは macOS では通り、高速な Linux ランナーでは毎回の再実行で失敗するため、リトライでは救えず、マージキューのどの PR のせいにされます。 時刻は自分で設定してください — ファイルにos.utime、行に明示的なcreated_at— 数分の差をつけ、その間にカットオフを選びます。これは、2 つのイベントが数秒差である本番の形でもあります。 - テストは本番のタイムアウト、レイテンシ予算、またはリトライ期限を継承してはなりません。
時間予算を持つコードはそれを設定から読み取り、それを上書きせずにそのコードを呼び出すテストは、CI ランナーがどれだけ速いかをアサートします。
呼び出し箇所にはこれが見えません。
hog テンプレートテストの
tester.invoke(...)はCDP_WATCHER_HOG_COST_TIMING_UPPER_MSから 550ms の予算を取り、executeLogTransformation(bytecode, record, globals, {})はDEFAULT_LOG_TRANSFORMATION_TIMEOUT_MSから 10ms を取ります。どちらも 2 ファイル先で定義され、テスト内ではどちらも名指しされていません。 そのようなテストはアイドル状態のラップトップでは大きな余裕で通り、負荷のかかったシャードでは失敗し、タイムアウトを誤った結果として報告します: 予算を使い果たした hog 変換はstatus: 'failed'を返すため、アサーションはExpected: "mutated" / Received: "failed"と読み取り、クロックも予算も名指ししません。 予算を明示的に、それが制約となり得ないほど十分大きく渡してください。そうすればテストは動作をアサートし、本番の数値は本番の決定のままになります。 結果オブジェクト全体をアサートし、その一部のフィールドだけをアサートしないでください:expect(outcome).toMatchObject({ status: 'mutated' })は失敗時にエラーを出力しますが、expect(outcome.status).toEqual('mutated')はそれを隠します。 - 実時間の長さをアサートしないでください。
expect(elapsedMs).toBeLessThan(2000)はコードではなくランナーを測定し、共有 CI ではランナーはそこで動いている他の何かです。 それは通常、無意味でもあります: テスト対象のコードがすでにより短い内部予算を持っている場合、その予算が先に作動するため、長さのアサーションが失敗するものになることは決してありません。 単体テストがスケールをカバーしなければならない場合は、スケールでの出力をアサートしてください — すべてのキーが存在し、何も切り捨てられていないこと — そしてタイミングはベンチマークに任せてください。 - 実ネットワーク / ライブの外部サービスは使わないでください。 境界をモックしてください。
- テスト間の順序付けはしないでください。 テストはどの順序でも、単独でも通らなければなりません。前のテストが残した状態に依存しないでください。
- テストは CI のマトリックスモードを継承してはなりません。
バックエンド CI は、persons-on-events のオンとオフなど、複数のモードで 1 つのテストを実行し、ローカル実行ではそのうちの 1 つを使います。
テスト対象のコードがモードで分岐する場合、テスト内でモードを設定し、重要な各モードに対して 1 つの
@parameterizedケースを用意してください。 - あなたが追加したテストが CI で失敗すると、たとえジョブが緑でもマージをブロックします。 Trunk は繰り返しの失敗を隔離できますが、隔離された失敗はジョブを失敗させません。
@skip/xfail/.skipは、1 行の理由とリンクされた issue なしでは使わないでください。 永続的にスキップされたテストは無駄な荷物です — 削除するか修正してください。.onlyを決してコミットしないでください (it.only/describe.only)。 これは 1 つのテストをスキップするのではなく、ファイル内の 他の すべてのテストをスキップします — スイートをほんの一部で緑にします。
PR を開く前に
PR テンプレートでは、「How did you test this code?」の項目でこれを尋ねられます。レビュアーが見る場所に、その根拠を記載してください。 テストのグループごとに 1 行で十分です:
"
test_cohort_queryに empty / single / oversized のコホートをカバーする 3 件のケースを追加しました — 先ほど修正した 500 を防ぎます。empty パスを検証する既存テストがなかったため、既存テストを拡張することはできませんでした。"
その 1 行が書けないのであれば、それは PR に含めるべきではないテストを見つけたということです。
関連スキル
- 既存の flaky テストを修正する →
/fixing-flaky-testsを使用してください(再現、根本原因の特定、検証)。修正によってカバレッジが追加または大幅に変更される場合にのみ、このスキルも使用してください。 そのスキルは上記のゲートを遡って適用します。flaky テストはすでにそのコストを証明済みであるため、それが捕捉するリグレッションを挙げられない場合、そこでは削除することも有効な結果です — 単なる安定化作業ではありません。 - flaky でない Playwright テストを作成する → まずこのスキルを使用して、ブラウザーテストがそのコストに見合うかどうかを判断してください。見合う場合は、具体的な手順に
/playwright-testを使用してください。