テスト方針(GeoSpatial機能 forDemo)
テスト方針(GeoSpatial機能 forDemo)
Section titled “テスト方針(GeoSpatial機能 forDemo)”GeoSpatial機能のテストで、上位の共通方針だけでは決まらなかった判断を記録する。 共通方針の内容はここに写さない(同じ結論が2箇所にあると片方だけ古くなる)。
飛行計画機能のTestPolicy_Flightplanning_forDemo.mdに 対応する位置づけだが、同ファイルがE2Eのテストパターンを列挙しているのに対し、本ドキュメントは 層の使い分けと、GeoSpatial固有の検出対象に絞る。
1. 記載範囲
Section titled “1. 記載範囲”飛行計画領域検索のAPI(POST /api/v1/geo/flight-plan-area/search・
POST /api/v1/geo/flight-plan-area/others/search)と、空域制限検索のAPI
(POST /api/v1/geo/airspace-restriction/{category}/search。パス変数ではなく分類ごとの別パスで、
一覧は5.3の1-1)を対象とする。APIを実装した順に節を追加する
(BusinessLogicSpecifications.md2.2と同じ方針)。
ただし下位層が先に入った場合は、実装済みの層だけを先に記録し、残る層は「未実装」と明示する
(APIとして動くのを待つと、先に入った層の判断が記録されないため)。
2者はどちらも全層が実装済みだが、節は分けたままにする。 3節は飛行計画領域検索の実績、 7節は空域制限検索の実績で、件数もカバレッジもそれぞれのAPIを対象に別々に測ったものである。 空域制限に固有の判断は7節にまとめ、3節には混ぜない(件数の出所が分からなくなるため)。
2. 上位の方針とその所在
Section titled “2. 上位の方針とその所在”テスト方針は5つの文書に分かれている。着手前に読む順に挙げる。
| 文書 | 定めていること |
|---|---|
| ArchitecturePolicy_Common.md 20章 | テスト方針の本体。20.1 UseCaseユニット/20.2 Handler/20.3 Infrastructure/20.4 統合/20.5 アーキテクチャの5層と、各層の検証対象 |
| CodingConventions_Common.md 38〜44章 | テストコードの書き方。38 方針/39 クラス命名/40 UseCase/41 Handler/42 メソッド命名/43 アサーション/44 ArchUnit |
| docs/implementation-guide.md「テスト」 | 5層の具体的な書き方。サンプルクラス名と、各層が「機能テストが通っていても検出できない」もの |
| .claude/rules/java/test.md | 道具(JUnit 6・AssertJ・Mockito)、命名、書くときの注意 |
| .claude/agents/java-reviewer.md | レビューで指摘する/指摘しないテストの問題 |
ADRにテスト専用のものはないが、ADR-015 ORM戦略が 挙げるMyBatisのデメリット「Column名・型ミスは実行時エラーになる」が、DB往復テスト(3.3)を必須とする根拠である。
3. 層の使い分けと、その層でしか検出できないもの
Section titled “3. 層の使い分けと、その層でしか検出できないもの”層の構成は共通方針20章に従う。GeoSpatialでどの層が何を捕まえるかを、実際に検出した内容で示す。
本節は飛行計画領域検索の実績である(件数も同APIのもの)。空域制限検索は7節を見る。
3.1 ドメインの不変条件(75件)
Section titled “3.1 ドメインの不変条件(75件)”座標・高度・形状・検索条件の値域と組み合わせ。有限性(NaN・Infinity)まで検証する。
値域の比較だけではNaNを通してしまう(NaNとの比較は常にfalseになりlongitude < -180をすり抜ける)。
Bboxの大きさの上限(幅50km・高さ50km・面積2500km²)は緯度に依存するため、東京付近の緯度で
境界の内外を作り分け、赤道をまたぐ範囲・南半球も確認する。この計算はドメイン単体でしか検証できない。
3.2 マッパーSQL(BoundSql、20件)
Section titled “3.2 マッパーSQL(BoundSql、20件)”<if>による句の付与・省略、enumキャスト、ST_Force3Dの適用箇所。
束縛パラメータ名までassertする。 単一時点(currentTime)と期間(currentTimeFrom/currentTimeTo)の
2分岐はSQL本文が同一で、差は束縛するプロパティだけである。本文のassertだけでは<if>条件を入れ替えても
両方が緑になる(実際にその状態のテストを書いてしまい、レビューで指摘された)。
getBoundSql(...).getParameterMappings()のプロパティ名を並びで確認する。
新しいマッパーXMLを足したらsrc/test/resources/mybatis-config-test.xmlの<mappers>にも登録する。
3.3 マッパーSQL(DB往復、30件)
Section titled “3.3 マッパーSQL(DB往復、30件)”実PostgreSQL/PostGIS(Testcontainers)に対して実行する。この層でしか検出できないもの:
resultMapの<constructor>の引数順のズレ。FlightPlanAreaSearchRowは28引数を位置で対応させており、 型が同じ列同士の入れ替わりはBoundSqlの文字列検査では分からない- 列名・型の誤り、enumキャスト(
::flight_planning.flight_plan_status_type)の妥当性 - PostGIS関数の振る舞い。
ST_Force3Dが与えるZ値、ST_AsGeoJSONの出力形、ST_Intersects+ST_MakeEnvelopeによる絞り込み、ST_X/ST_Yによる円の中心の復元 COUNT(*) OVER ()がlimit適用前の件数を返すこと
検索対象の行はFlightPlanRepositoryの書き込み経路(insertDraft→insertRevision→
advanceToRevision)で作る。 plan_geometry・top_bottom_3d_geometryはinsertAreaがSQLで導出するため、
テスト用にジオメトリを組み立てると本番の導出ロジックが検証対象から外れる。
設計判断の実証にも使う。「bboxの判定対象をplan_geometryにする」(area_type=CIRCLEのときgeometryは
中心点のPointであり、円の一部だけがbboxに重なる計画を取りこぼす)という判断は、
中心をbboxの外に置いた円が該当することを実データで確認している。
3.4 Handler(MockMvc、23件)
Section titled “3.4 Handler(MockMvc、23件)”HTTPステータス、レスポンスJSON、検索条件の変換、UseCase呼び出し。
クライアントが送るJSONのnullを必ず踏む。 生成DTOのBean Validationはこれを弾かない。
limit・statusは生成DTOがフィールドの既定値を持つが、setLimit(null)/setStatus(null)がそれを
上書きするためnullがハンドラーまで届き、アンボックスやnull検査なしの走査でNPE(500)になる。
bboxの@Sizeは要素数しか見ないため要素のnullも同様である。
OASにminLength・patternの宣言がない文字列(dipsFlightPlanId)も、値オブジェクトの不変条件違反が
そのまま500になる。いずれも実際に500を出した経路で、回帰テストとして残している。
422は業務仕様5.1の1-4が列挙するケースを網羅する(bbox上限超過・値域外・null要素、
時刻条件の排他と片側のみ指定と逆順、範囲とIDのいずれも未指定)。
3.5 UseCase単体(8件)
Section titled “3.5 UseCase単体(8件)”リポジトリはMockito、トランザクションはテストダブル。両ユースケースは委譲が主な仕事なので、 検索条件をそのまま渡すこと・対象組織が固定値であること・読み取り専用のトランザクション属性で 実行することを見る。
最後の1点のため、共通方針20.1が挙げるNoopTransactionManagerではなく、渡された
TransactionOptionsを記録するテストダブルを使う。NoopTransactionManagerは属性を捨てるため
readOnlyを確認できない。
共通方針20.1が挙げるテスト対象6つのうち、参照のみの本ユースケースに当てはまるのは3つである。
| 20.1のテスト対象 | 本ドメインでの扱い |
|---|---|
| 正常系 | 検索条件の受け渡しと戻り値を確認する |
| 対象データなし | リポジトリが0件を返した場合、そのまま返すことを確認する |
| Repository例外発生時 | RepositoryExceptionを包まず素通しすることを、同一インスタンスが上がることで確認する。TransactionManagerの契約は「例外が伝播したらロールバック」であり、途中で包んだり握るとその契約が静かに壊れる |
| 入力不正 | 当てはまらない。入力の検証はHandler層(FlightPlanAreaSearchRequestParser)と検索条件の値オブジェクトが済ませており、ユースケースが受け取る時点で不正な条件は存在しない |
| 重複エラー | 当てはまらない。更新系の関心事 |
| 状態遷移不正 | 当てはまらない。同上 |
3.6 End-to-End(8件)
Section titled “3.6 End-to-End(8件)”@SpringBootTest(webEnvironment = RANDOM_PORT)+Testcontainers+RestClient。構成は
FlightPlanningE2ETestに倣い、Testcontainers設定
(FlightPlanningE2ETestcontainersConfiguration)も再利用する。検索対象の行を飛行計画機能のAPI
(仮登録→本登録)で作るため、必要なスキーマとAssetシードが同じであり、注釈を揃えることで
Springのコンテキストキャッシュを共有できる。
この層でしか確認できないもの:
Content-Type: application/geo+jsonが実際に返ること。OASのproduces宣言が実サーバの コンテンツネゴシエーションに反映されているかは、MockMvcのstandaloneSetupでは見られない- 飛行計画の登録(書き込み)から領域検索(読み出し)までがHTTP越しに通ること。DB往復(3.3)と Handler(3.4)は層ごとの確認に留まる
複数の@Testが同一コンテナ・同一DBを共有するため、内容の確認は自分が作成したflightPlanIdを
条件に指定して行う。bboxで絞る確認だけはテストごとに離れた座標を使い、他テストの計画と
重ならないようにする(FlightPlanningE2ETestは141.0〜141.35/43.0〜43.1を使う)。
他Operator向けは、収集した計画を作るAPIが本ドメインの対象外のため、経路が通ること (200・空のFeatureCollection)と自組織の計画が混ざらないことに留める。収集データありの検証は DB往復(3.3)が担う。
3.7 アーキテクチャ
Section titled “3.7 アーキテクチャ”既存のarch/ArchitectureTestに委ねる。domain・usecaseがgenerated・Spring・MyBatisに
依存しないことを含む。パッケージを足したので通過を確認している。
メソッド名はtest.mdに従いtestXxxで統一する。 文書間に矛盾があるため、従う先を明示する。
| 文書 | メソッド命名について |
|---|---|
| CodingConventions_Common.md 42章 | 「形式・命名スタイルは開発者に任せる」 |
| CodingConventions_Flightplanning_forDemo.md 7章 | <targetMethod>_when<Condition>_<ExpectedResult>に限定し、testXxxを禁止と明記 |
| .claude/rules/java/test.md | 「混在を過渡期の時限措置として許す。どちらでもよい新規ファイルならtestXxxを選ぶ」 |
飛行計画forDemoの7章は同ドメインのスコープであり、GeoSpatialには及ばない。
java-reviewer.mdも「メソッド名の混在は指摘しない」としてtest.md側に立っているため、
本ドメインはtest.mdに従う。契約は@DisplayName(日本語で条件と期待結果の両方が読み取れる文)に置く。
「統一する」は新規ファイルに対する指針である。 test.mdは「新規はそのファイルの既存スタイルに
合わせる」も定めているため、既存ファイルへテストを足すときはそのファイルの形式に合わせる
(既存名の改名は求めない)。空域制限の既存6クラスがこれに当たる(7.6)。
クラス名は対象クラス名+Testを基本とし(CodingConventions_Common.md 39章)、テストの種類を
接尾辞で区別する。同章の表もFeatureAMapper→FeatureAMapperBoundSqlTestと接尾辞を付ける形を示している。
| 種類 | 接尾辞 | 例 |
|---|---|---|
| BoundSql | BoundSqlTest | FlightPlanAreaSearchMapperBoundSqlTest |
| DB往復・統合 | IntegrationTest | FlightPlanAreaRepositoryImplIntegrationTest |
| Repository実装の単体(Mapperモック) | FailureTest | AirspaceRestrictionRepositoryFailureTest |
| それ以外 | Test | FlightPlanAreaHandlerTest |
IntegrationTest接尾辞は39章の表に無いが、本リポジトリの統合テスト19ファイルが一貫して使っており、
Testcontainersを要する(実行が遅くDockerが必要な)テストであることが名前で分かる利点があるため踏襲する。
FailureTest接尾辞も39章の表に無いが、AssetRepositoryFailureTestを含む5ファイルが一貫して使っている。
基底名がPort名(実装クラス名…RepositoryImplではない)である点も含めて既存に倣う。
5. カバレッジの扱い
Section titled “5. カバレッジの扱い”目標はC1 90%(TestPolicy_Flightplanning_forDemo.mdの「カバレッジ」節)。 同節の「目標値達成のためだけの機械的なテスト追加は行わない」に従い、未カバー分岐を分類して 報告する運用とする。
測定は2つに分かれており、合算した値は持たない。 飛行計画領域検索(3節)と空域制限検索(7節)は それぞれ自分のクラス一式を対象に別々に測ったもので、一方を測り直さずに足すと根拠のない数字になる。 どちらも目標には未達である。
| 測定の対象 | 未カバー分岐 | 分岐C1 | 内訳 |
|---|---|---|---|
| 飛行計画領域検索(3節) | 60 | 81.3% | 5.1 |
| 空域制限検索(7節) | 25 | 87.0% | 5.2 |
Bboxは両APIが共有するため、飛行計画領域検索の側にだけ数える。
なお、null検査にcontains(null)を使うとList.ofが返す不変リストでNPEになる(仕様)。
stream().anyMatch(Objects::isNull)を使う。
5.1 飛行計画領域検索の未カバー60分岐
Section titled “5.1 飛行計画領域検索の未カバー60分岐”| 分岐数 | 内容 | 追加しない理由 |
|---|---|---|
| 約50 | @NullMarkedな型に対するnullガード | test.mdのとおりテストではNullAwayが無効なのでnullを渡せはするが、型が禁止している値を通す試験になる |
| 4 | enumの合成コード(values/valueOf) | コンパイラ生成 |
| 1 | Bboxの面積上限 | 球面の面積は幅×高さを上回らないため、幅50km・高さ50km以内では到達不能(同じ理由をコード側にも記載) |
| 1 | ||の短絡 | 計上上の分岐 |
5.2 空域制限検索の未カバー25分岐
Section titled “5.2 空域制限検索の未カバー25分岐”全193分岐に対する値で、手書きコードだけを対象にしている。 生成コードのfromValueのループを
含めると30/213・85.9%になる。
| 分岐数 | 内容 | 追加しない理由 |
|---|---|---|
| 20 | @NullMarkedな型に対するnullガード(ドメインのコンパクトコンストラクタとRepository実装の引数検査) | 5.1と同じ。型が禁止している値を通す試験になる |
| 1 | ||の短絡 | 計上上の分岐。validAtと期間の併用を弾く条件(validFrom != null || validTo != null)で、validAt+validFromの組み合わせが左辺で短絡する |
| 4 | 値域ガードとinstanceof(AirspaceRestrictionSearchResultのtotalCount < 0、AltitudeBounds#isEmpty、AirspaceRestrictionRepositoryImplがMultiPolygonを要求する2箇所) | totalCountはCOUNT(*)の値で負にならない。AltitudeBoundsは下限・上限の片側だけを持つ行をフィクスチャが持たず、この分岐で応答は変わらない。残る2つは種別の食い違いをRepositoryExceptionへ包む経路で、同じ形の判定をPolygon側が代表して踏んでいる(#236の成果物への追加になるため足さない。7.7) |
例外の捕捉は未カバー分岐に数えない。 jacocoのBRANCHカウンタは例外の辺を数えないため、
到達しないcatch(AirspaceRestrictionSearchRequestParserのstatusの解決。同詳細設計の5-3節)は
未実行の命令としてだけ残る。
6. 未対応
Section titled “6. 未対応”altitudeReference=WGS84のFeature:*_geometry_wgs84列への充填処理が未実装のため、 当該Featureを返す経路自体がまだない(FlightPlanAreaExtentsのTODOと followups.md参照)
本節は飛行計画領域検索のものである。空域制限検索の未対応は7.7にある。
7. 空域制限検索
Section titled “7. 空域制限検索”制限分類ごとに分かれたエンドポイントを対象とする。処理フローの正は BusinessLogicSpecifications.mdの5.3節で、 本節はそれを再掲せず、テストの層をどう使い分けるかだけを書く。
3節との重複は置かない。飛行計画領域と同じ判断(3.4の「JSONのnullを必ず踏む」、3.5のUseCase単体の
扱い、3.7をアーキテクチャテストに委ねる)はそのまま適用し、ここには違うところだけを挙げる。
3節は飛行計画領域検索に範囲を限った節だが、これらの判断はAPIに依らないため空域制限へも持ち越す。
7.1 層ごとの実装状況
Section titled “7.1 層ごとの実装状況”| 層 | 件数 | 出所 |
|---|---|---|
| ドメインの不変条件 | 43件(AirspaceRestrictionTest 24・AirspaceGeometryTest 13・AirspaceRestrictionSearchTest 6) | #236 |
| Repository実装の単体(Mapperモック) | 9件(AirspaceRestrictionRepositoryFailureTest) | #236・PR-04 |
| マッパーSQL(BoundSql) | 11件(AirspaceRestrictionMapperBoundSqlTest) | #236 |
| マッパーSQL(DB往復) | 17件(AirspaceRestrictionRepositoryImplIntegrationTest) | #236 |
| Handler(MockMvc)・RequestParser単体 | 40件(AirspaceRestrictionHandlerTest 20・AirspaceRestrictionSearchRequestParserTest 20) | PR-04 |
| UseCase単体 | 4件(SearchAirspaceRestrictionUseCaseTest) | PR-04 |
| ResponseMapper単体 | 31件(AirspaceRestrictionResponseMapperTest 24・AirspaceGeometryResponseMapperTest 7) | PR-04 |
| End-to-End | 9件(AirspaceRestrictionE2ETest。7.8) | PR-04 |
下位4層は#236(空域制限ドメインのDBアクセス)が、上位4層はPR-04(検索APIの実装)が提供する。 Repository実装の単体だけは両者にまたがり、形状を解釈できない行の2件をPR-04が足した(7.2)。 全層が実装済みで、分岐カバレッジの実測は5.2にある。
「実装済み」は検出対象を満たしていることを意味しない。 BoundSqlには7.3が挙げる欠けがある。
7.2 Repository実装の単体(Mapperモック)
Section titled “7.2 Repository実装の単体(Mapperモック)”共通方針20.3が挙げるInfrastructureテストは BoundSql と Testcontainers の2つだが、本ドメインは
#236の時点で既にあった4クラス(Asset・FlightPlan・Notification・Pilot)に倣い、3つ目としてMapperをモックした
Repository実装の単体を持つ(FlightPlanAreaRepository側にはこの層が無い)。
DataAccessExceptionがRepositoryExceptionへ包み直されること、および行の内容がドメインの
不変条件を満たさないときに原因がRepositoryExceptionとして伝わることを見る。対象は3種類ある。
| 種類 | 例 | この層を要する理由 |
|---|---|---|
| DDLが禁じている行 | 未知のgeometry_type(ENUM型が投入を拒む)・geometry_type=CIRCLEで円の3列がNULL(ck_airspace_restriction_circle) | DB往復はDDLが通す行しか作れず、この層でしか読ませられない |
| DDLは通すがドメインが拒む行 | geometry_type='POLYGON'に実ジオメトリがMultiPolygon | ck_airspace_restriction_geometryはPOLYGONとMULTIPOLYGONの両方を許し、両者を結ぶCHECKはck_airspace_restriction_circleのCIRCLE分岐にしかない。DB往復でも作れるが、Mapperを差し替えるほうが直截である |
| 形状をWKTとして解釈できない行 | POLYGON EMPTY(面を持たない)。geometry列とWGS84側のジオメトリ列の両方 | DDLのCHECKは種別とSRIDしか見ないため通る。WktCodecは格納値をメッセージに含むIllegalArgumentExceptionを投げるため、RepositoryExceptionのメッセージに格納値が出ないことも併せて固定する |
7.3 マッパーSQL(BoundSql)— 有効期間の束縛が交差する
Section titled “7.3 マッパーSQL(BoundSql)— 有効期間の束縛が交差する”3.2の「束縛パラメータ名までassertする」は空域制限でも効く。取り違えの見えにくさはむしろ一段上がる。
単一時点(validAt)と期間(validFrom/validTo)の2分岐は、どちらも
valid_from <= X AND valid_to >= Yという同じ形をとる。期間側だけ列と束縛が交差する
(valid_from <= #{validTo}・valid_to >= #{validFrom})。重なり判定として正しい形だが、
#{validFrom}と#{validTo}を入れ替えてもSQL本文は文法的に成立するため、本文のassertでは落ちない。
[!WARNING] 現状の
AirspaceRestrictionMapperBoundSqlTestはこの検査を持たない。 同テストの期間分岐はassertThat(sql).contains("valid_from <= ? and valid_to >= ?")という本文のassertだけで、getParameterMappings()を参照していない(同APIを使うのはFlightPlanAreaSearchMapperBoundSqlTestのboundPropertiesOfのみ)。入れ替えを実際に捕まえているのはDB往復(7.4)である。
search_whenPeriodIsGiven_filtersByOverlapが指定期間の端を制限の有効期間に対して非対称に置いて おり(片側だけが重なる2件)、入れ替えると該当0件になって落ちる。検出はできているが、 担うべき層がずれている。 指定期間が制限の有効期間に内包される形へ書き直すと、 BoundSql・DB往復のどちらも緑になる。7.7に申し送る。
categoryとstatusに<if>は付かない。 statusは未指定でも既定値ACTIVEが必ず1値だけ渡るため
(5.3の1-2-2)、飛行計画領域のstatus(複数値・未指定は絞り込みなし)とは条件句の構造が違う。
<if>の有無を飛行計画側から流用しない。
なお<if test="validFrom != null">はvalidToを見ないため、単一時点と期間の排他はSQL層には無い。
排他を作るのはAirspaceRestrictionValidity(sealed)とRequestParserの422であり、BoundSqlテストは
素のMapに両方を詰められる。この層に排他の検証を期待しない。
7.4 マッパーSQL(DB往復)— 行はテストがSQLで直接作る
Section titled “7.4 マッパーSQL(DB往復)— 行はテストがSQLで直接作る”3.3は「検索対象の行は書き込み経路で作る」と定めるが、空域制限には適用できない。
AirspaceRestrictionRepositoryが読み取りのみを提供し、アプリケーションが行を作る経路が
存在しないためである(収集が実装対象外。docs/data-model/airspace-er.mdの「スコープ」)。
JdbcTemplateとフィクスチャで行を直接INSERTする。3.3が警戒する「本番の導出ロジックが
検証対象から外れる」問題は、導出する処理そのものが無いため起きない。
代わりにフィクスチャ自身が正しさの拠りどころになる。 書き込み経路を通さないぶん、
top_bottom_3d_geometryの上下面の並び(coordinates[0]が上面。正は
airspace-er.mdの「上面・下面は3次元のgeometryで持つ」)のような
DBの取り決めは、フィクスチャが守っていなければどこにも現れない。飛行計画領域では、誤った並びの
フィクスチャが不具合のほうを固定していた(PR #275。
実装を直す際に既存テストのフィクスチャも直している)。上下面を持つフィクスチャは上面を先に置き、
上下でZ値を変える。
この定めはEnd-to-End(7.8)にも及ぶ。 行を作る経路が無いのはHTTP越しでも同じで、
検索対象の行はテストがJdbcTemplateで直接INSERTする。デモ用の投入データ
(db/seed/local/)は待たない。上下面の並びとZ値についても同じ制約がかかる。
7.5 Handler層の使い分け
Section titled “7.5 Handler層の使い分け”変換規則の網羅はRequestParserの単体テストに置き、Handler(MockMvc)は 「分類ごとの全経路が同じParserを通ること」と「HTTP越しにしか確認できないこと」に絞る。
飛行計画領域(3.4)は検証の分岐をすべてMockMvcで踏んでいるが、空域制限は分類ごとのエンドポイントが 同じ変換規則を共有する。MockMvcに寄せると、1つの分岐を確かめるのに代表させるエンドポイントを 毎回選ぶことになり、代表させなかった経路は同じ規則を通っている保証を持たない。 規則はHTTPと無関係に決まるので、規則はParserで1度だけ、経路の対応はHandlerで確かめる。
個々のテストケースの割り付けは issue-260の詳細設計の8節にある。 未カバー分岐の分類は同8-4節にあり、実測は5.2の表が持つ。
ResponseMapperは単体で見る。 1件の空域制限を何件のFeatureへ広げるかの判定は、 元の列の組み合わせ(上面リングの有無・水平形状の次元・スカラーの高度の有無)で分岐が増える。 Handler(MockMvc)に寄せるとJSONの組み立てと判定の失敗が区別できず、DB往復に寄せると 分岐ごとに行を投入することになる。判定はMapperの単体で網羅し、Handlerは配線の確認1件に留める。 割り付けは同詳細設計の15-2節にある。
3.4の「クライアントが送るJSONのnullを必ず踏む」はそのまま効く。本APIでのテストケースは
同8-2節に、setStatus(null)が初期値を上書きする機序は同5-1節にある。
7.6 命名
Section titled “7.6 命名”クラス名・接尾辞は4節に従う。AirspaceRestrictionMapperBoundSqlTest・
AirspaceRestrictionRepositoryImplIntegrationTest・AirspaceRestrictionRepositoryFailureTestと、
検索APIの6クラス(AirspaceRestrictionHandlerTest・AirspaceRestrictionSearchRequestParserTest・
SearchAirspaceRestrictionUseCaseTest・AirspaceRestrictionResponseMapperTest・
AirspaceGeometryResponseMapperTest・AirspaceRestrictionE2ETest)が対象である。
メソッド名は4節の但し書きが効く。 7.1の表が挙げる#236由来の6クラス78メソッドはすべて
<メソッド>_when<条件>_<期待結果>形式で、testXxxは0件である。既存クラスへ追加するときは
そのスタイルに合わせ、上記の6クラスはtestXxxとする。
7.7 未対応
Section titled “7.7 未対応”- 有効期間の束縛パラメータ名のassert: 7.3のとおり
AirspaceRestrictionMapperBoundSqlTestに 欠けており、束縛の入れ替えの検出をDB往復の境界ケースに頼っている。FlightPlanAreaSearchMapperBoundSqlTest#boundPropertiesOf相当を足し、期間分岐で束縛がvalidTo・validFromの順になることを確認する。壊れているわけではなく、層の割り付けの是正である。 あわせて同テストの「向きの取り違えをここで固定する」というコメントも、実際には固定できていないため 実態に合わせる。#236の成果物への追加になるため本PRでは行わない - 1行の不備で検索全体が失敗することの是非: 形状を解釈できない行が1件あると、
searchは 該当行を除いて続行せず全体をRepositoryExceptionにする(issue-260の詳細設計の 10-1節)。InconsistentRowExceptionのjavadocが宣言する「当該行だけを結果から除いて続行してよい」 とは扱いが違う。是非はfollowups.mdで決める status=DISAPPEAREDの検索: 遷移させる主体がいないため実データはACTIVEに限られる (followups.md)。該当0件になることの確認にとどまる- デモ用投入データそのものの妥当性:
db/seed/local/に空域制限のファイルが無く、投入データは デモ実施時に用意する。テストは自分で行を作るため経路の確認は7.8で済んでいるが、 実データが本APIで期待どおり見えるかはデータが用意された時点で確認する
7.8 End-to-End
Section titled “7.8 End-to-End”@SpringBootTest(webEnvironment = RANDOM_PORT)+Testcontainers+RestClient。層の使い分けは3.6と同じで、
HTTP越しにしか確認できないことと、経路が通ることに絞る。 分岐の網羅は7.5の割り付けが持つ。
3.6との違いは2つある。
| 点 | 飛行計画領域(3.6) | 空域制限 |
|---|---|---|
| Testcontainers設定 | FlightPlanningE2ETestcontainersConfiguration(DDLを名指しでコピーし、Assetシードと模擬DIPSを要する) | 共通のTestcontainersConfiguration。db/schema/をディレクトリごと適用するためairspace_restriction.sqlが入り、追加のシードを要さない |
| 検索対象の行 | 飛行計画機能のAPI(仮登録→本登録)で作る | テストがJdbcTemplateで直接INSERTする(7.4) |
この層でしか確認できないもの:
Content-Type: application/geo+jsonが実際に返ること(理由は3.6と同じ)- DBの行が形状とプロパティを保ってFeatureになるまで通ること。WKTの復元からFeatureの組み立てまでを 連結して踏むのはこの層だけで、7.5のResponseMapper単体はドメインオブジェクトから始まる
- 高度を持つ行が
AGL・WGS84の2件へ展開されること。実DBから読み出したZ値でも判定が成り立つことを見る - 検索条件(
category・bbox・status・有効期間)による絞り込みが実DBで効くこと。 Handler(MockMvc)はUseCaseをモックするため条件として渡るところまでしか見ておらず、 DB往復(7.4)は検索条件を手で組む。RequestParserが組み立てた条件がSQLへ届くところまでを 連結して踏むのはこの層だけである。とくにstatusの既定値(ACTIVE)とOptional<AirspaceRestrictionValidity>の判別は、HTTPのリクエストボディから通してのみ固定できる bboxが大きさの上限を超える場合に422のProblem Detailsが実サーバから返ること
投入した行はコミットされる。 サーバが別コネクションで動くため@Transactionalによるロールバックが
効かない(AirspaceRestrictionRepositoryImplIntegrationTestが付けられるのはwebEnvironment = NONEだから
である)。@AfterEachで投入したidをDELETEし、他テストと重ならないbboxの座標を使う
(3.6と同じ理由。同一コンテナ・同一DBを共有する)。