Skip to content

New Feature: #[SkipOpenApi] 属性で auto-assert を個別テスト単位でオプトアウト#55

Merged
wadakatu merged 7 commits into
mainfrom
feature/skip-openapi-attribute
Apr 23, 2026
Merged

New Feature: #[SkipOpenApi] 属性で auto-assert を個別テスト単位でオプトアウト#55
wadakatu merged 7 commits into
mainfrom
feature/skip-openapi-attribute

Conversation

@wadakatu
Copy link
Copy Markdown
Collaborator

概要

auto-assert モード下で個別テストを契約検証から除外するための #[SkipOpenApi] 属性を追加します。わざと spec 違反を返すテストや、契約が固まっていない実験的エンドポイントを扱うテストで利用できます。

変更内容

反射ベースの属性で method-level / class-level の双方をサポートし、reason パラメータを受け取れます。method-level が class-level を上書きします。

  • src/SkipOpenApi.php: 属性クラス(TARGET_CLASS | TARGET_METHODreason: string = ''
  • src/SkipOpenApiResolver.php: OpenApiSpecResolver と同じ流儀の reflection ベース resolver trait(shouldSkipOpenApi() / resolveSkipOpenApiReason()
  • src/Laravel/ValidatesOpenApiSchema.php:
    • maybeAutoAssertOpenApiSchema() で skip なら早期 return(検証なし・OpenApiCoverageTracker にも未記録 = 未カバレッジ扱い)
    • assertResponseMatchesOpenApiSchema() 明示呼び出しは 常に走る(option A の意図表明尊重)。ただし #[SkipOpenApi] 付きテストで明示呼び出しすると E_USER_DEPRECATED を発行し、矛盾した意図を可視化
    • 警告ハンドラは $skipWarningHandler static プロパティ経由で差し替え可能(テスタビリティのため)
  • tests/Unit/SkipOpenApiResolver{,ClassLevel}Test.php: resolver 単体テスト(no attr / method / class / method-reason override)
  • tests/Unit/ValidatesOpenApiSchema{,ClassLevel}SkipTest.php: auto-assert スキップ挙動 + 警告挙動
  • tests/Integration/Laravel/AutoAssertIntegrationTest.php: Testbench 経由の method-level skip 統合テスト
  • README.md: Auto-assert セクション下に #[SkipOpenApi] の使い方・セマンティクス・警告挙動を追記

設計メモ(レビュー観点)

  • option A(緩い)セマンティクス: 属性は auto-assert のみ抑制。明示呼び出しまで抑制すると class-level skip + 一部 method での明示検証という正当パターンが書けなくなるため。
  • 警告を E_USER_DEPRECATED: phpunit.xml.dist は failOnWarning=true のため E_USER_WARNING は使えない。failOnDeprecation はデフォルト false なので deprecation 通知はテストを落とさず PHPUnit サマリに可視化される(Symfony/Laravel でも馴染みのパターン)。
  • method-level が class-level を override: 属性は on/off トグルを持たないため net skip 挙動は同じだが、reason は method 側が優先されるよう実装。

品質ゲート

  • PHPUnit: 217/217 green(新規 10 テスト)
  • PHPStan (level 6): errors なし
  • php-cs-fixer: violations なし

関連情報

Add a reflection-based resolver that detects method-level or class-level
#[SkipOpenApi(reason: '...')] attributes, with method-level taking
precedence over class-level for fine-grained opt-out.

Refs #40
… warning

maybeAutoAssertOpenApiSchema() now early-returns when the current test
method or its class carries #[SkipOpenApi], so no validation or coverage
recording happens on intentionally skipped tests. Calling
assertResponseMatchesOpenApiSchema() explicitly on a skipped test still
runs the assertion but emits E_USER_DEPRECATED via a swappable
$skipWarningHandler to flag the contradictory intent.

Refs #40
Add an Auto-assert subsection covering method-level and class-level
usage of #[SkipOpenApi], the optional reason parameter, and the
deprecation-warning behavior when assertResponseMatchesOpenApiSchema()
is called explicitly on a skipped test.

Refs #40
…ribute

The resolveSkipOpenApiReason() helper returned an empty string both when
no #[SkipOpenApi] attribute existed and when one existed with an empty
reason, conflating two distinct states. Drop the helper and have callers
work directly with the nullable attribute.

assertResponseMatchesOpenApiSchema() now resolves the attribute once and
passes it to emitSkipOpenApiWarning(), avoiding a duplicate reflection
pass on the skip-plus-explicit-call path. maybeAutoAssertOpenApiSchema()
uses the same null check to align the two entry points.
…punit default config

trigger_error(E_USER_DEPRECATED) alone is effectively silent under
PHPUnit's default display configuration — only a "1 deprecation" tally
appears in the summary, not the actual message body. Operators relying
on the run summary never saw why the contradictory-intent warning was
raised.

Also emit the formatted message to STDERR so CI logs always include the
class/method/reason, regardless of whether
displayDetailsOnTestsThatTriggerDeprecations is enabled downstream. The
trigger_error call is retained so tooling that counts deprecations keeps
working.
…vel warning, and POST skip

Fills coverage gaps identified during PR review:

- New ValidatesOpenApiSchemaDefaultWarningHandlerTest exercises the
  default trigger_error(E_USER_DEPRECATED) path via set_error_handler
  so a future refactor cannot silently drop the deprecation emission.
- ValidatesOpenApiSchemaSkipTest asserts the warning message contains
  the var_export-formatted "reason: '...'" segment, locking in the
  user-facing format.
- ValidatesOpenApiSchemaClassLevelSkipTest adds an explicit-assert case
  that verifies the class-level reason surfaces in the warning and that
  the handler is reset in tearDown.
- AutoAssertIntegrationTest adds a POST skip case through the Testbench
  integration path so a regression that only consulted skip on GET would
  be caught.
- Clarify that a method-level #[SkipOpenApi] fully shadows the class-
  level attribute. The prior wording ("the reason of the method-level
  attribute wins") implied only the reason field was overridden, but in
  reality the method-level attribute replaces the class-level one
  entirely during resolution.
- Document that coverage is recorded when a #[SkipOpenApi] test still
  calls assertResponseMatchesOpenApiSchema() explicitly. The previous
  wording implied the skip attribute always suppressed coverage.
- Note that class-level attributes on abstract parents are not inherited
  by subclasses, since resolution uses ReflectionClass on the direct
  class only.
@wadakatu
Copy link
Copy Markdown
Collaborator Author

レビューフィードバック対応 (4 commits)

/pr-review-toolkit:review-pr の指摘を反映しました。

🔴 Critical 対応

  • warning の可視性: trigger_error(E_USER_DEPRECATED) は PHPUnit デフォルトでは "1 deprecation" の数字のみ表示され、メッセージ本文は displayDetailsOnTestsThatTriggerDeprecations を明示設定しない限り不可視。CI で見逃されるリスクを解消するため STDERR への直接書き込み + trigger_error の併用 に変更。STDERR 書き込みはどの PHPUnit 設定でも可視。(ce14a79)

🟠 Important 対応

  • reflection 重複呼び出し: skip + explicit call の経路で findSkipOpenApiAttribute() が 2〜3 回呼ばれていたため、assertResponseMatchesOpenApiSchema() 冒頭で 1 回だけ resolve して emitSkipOpenApiWarning(SkipOpenApi $attr) に渡す形に。副作用として resolveSkipOpenApiReason() の「attribute なし」と「attribute あり・reason 空」を混同する ambiguity も解消。(277086f)
  • テストギャップ:
    • default warning handler (STDERR + trigger_error fallback) 自体のテストが無かった → ValidatesOpenApiSchemaDefaultWarningHandlerTest 新規追加、set_error_handlerE_USER_DEPRECATED を捕捉
    • class-level skip での explicit-call warning 未検証 → class_level_skip_emits_warning_with_class_reason_on_explicit_assert 追加
    • warning message の reason: '...' フォーマット (var_export) 未検証 → warning_message_includes_reason_when_provided 追加
    • 統合テストが GET のみ → POST 版追加
    • ValidatesOpenApiSchemaClassLevelSkipTest に handler setUp/tearDown が無く cross-test contamination リスク → 同ファイルに handler 管理を追加 (a36734c)
  • README 不正確さ:
    • 「method-level takes precedence (reason wins)」→ 誤解を招く(属性全体が shadow される) → 「fully shadows」に訂正
    • 「skip で coverage 記録されない」→ explicit call の場合は記録されるので条件付きに訂正
    • 親クラス #[SkipOpenApi] が継承されない制約を新規注記 (dbffbf5)

🟡 その他

  • SkipOpenApiResolver の WHAT コメント削除
  • $skipWarningHandler docblock から "option A semantics" jargon を平文化

品質ゲート

  • PHPUnit: 221/221 green(新規 4 テスト: default handler, reason format, class-level warning, POST skip)
  • PHPStan lv6 / php-cs-fixer: 引き続き clean

@wadakatu wadakatu self-assigned this Apr 23, 2026
@wadakatu wadakatu merged commit cf48339 into main Apr 23, 2026
8 checks passed
@wadakatu wadakatu deleted the feature/skip-openapi-attribute branch April 23, 2026 01:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[E1] Add #[SkipOpenApi] attribute to opt out of auto-validation per test

1 participant