学習・技術検証用デモ — 本システムは実際の銀行サービスではありません。

公開情報をもとに構築した教育目的のシステムです。実在する個人情報・口座情報・機密情報・認証情報等を入力しないでください。

ミニ銀行ACADEMY SYSTEM LEARNING
教材メニュー
学習進捗0 / 134
あと134項目
INTERMEDIATE · 中級COURSE 04 · SOFTWARE ENGINEERING

テストの防御線

Minibankに実在するテストを、実行層だけでなく『どんな事故を実際に防いだか』から読み解きます。

01 / 11
SOFTWARE ENGINEERING · ソフトウェア工学

境界、transaction、並行制御、脅威、テスト、障害対応をFailure Firstで学ぶ。

先に学ぶ内容 — Prerequisite: 基礎 / 銀行システム

学ぶこと — LEARNING GOALS

このページで学ぶこと

  • Regression・API・E2E・Concurrency・Privilegeの役割
  • テストが発見した実バグ3件
  • 合格件数が保証する範囲と限界

体験13 · TESTING(テストの防御線)

「一度動いた」と「いつも安全」は同じか確かめよう

まずは予想をひと言。本文を読んだ後、教材内の参照元で実際のテスト記録を確認します。

体験学習 · STEP 1 / 5
  1. 01
    まず予想する — PREDICT

    まずここだけ:思ったことをひと言書く

    プログラムが一度正しく動いたら、テストはもう不要でしょうか?

    正解でなくて大丈夫です。「〜だと思う」から始めてみましょう。

    ヒント1を見る

    「正常に動くこと」を確かめるテストと、「わざと失敗させて」確かめるテストは、同じものでしょうか?

    ヒント2を見る

    同時に2つの処理が重なったときの問題は、順番に1回ずつ試すテストで見つかるでしょうか?

    もっと詳しく:この問いで使う専門用語
    • Regression test(回帰test)は、過去に守れていた約束が変更後も壊れていないかを確認するtestです。
    • concurrency test(同時実行test)は、複数の処理をわざと同時に進行させて競合を確認するtestです。
    • failure recovery test(失敗回復test)は、処理の途中でわざと失敗させ、記録が半端に残らないかを確認するtestです。
    • 状態機械testは、各状態と許可された遷移が正しく守られているかを確認するtestです。
まずはひと言だけで大丈夫です。

この後の本文を最後まで読んでから、実際に操作して確かめるステップへ進みます。

日常語で理解する — PLAIN LANGUAGE

まず、ここだけつかむ

1

テストは『正常に振込できる』だけでなく、攻撃・再送・終了状態・途中失敗でも安全かを確かめる防御線です。

2

実際に、function overloadによるPGRST203、failed OTPをexpiredと誤る状態判定、生DB error漏洩の3件をtestが発見し、再発防止caseとして残しました。

3

DB/RPC、API、E2E、Privilege、Concurrencyは守る境界が違います。280件や11件という数字だけでなく、何を試していないかも読みます。

順番に理解する — DIFFICULT PARTS, STEP BY STEP

難しいポイントを、順番にほどく

専門用語を覚えることより、「なぜ必要か」「このシステムのどこで使うか」を理解することが目標です。

01

DB regression test

やさしく言うと

過去に守れていた制約や権限が、変更後も壊れていないかをDBへ直接確認します。

なぜ必要?

UIテストだけではPUBLIC EXECUTE、RLS bypass、直接UPDATEなどを見落とします。

Minibankでは

JavaScript suiteがSupabase clientとCLI queryを使い、会計制約、RLS、関数権限、状態遷移を検査します。

02

API / E2E test

やさしく言うと

APIの入口と、ブラウザからの利用者操作をそれぞれ確認します。

なぜ必要?

DBが安全でもtoken受渡しや入力変換、画面の導線が壊れることがあります。

Minibankでは

API回帰とPlaywrightのE2Eが別の境界を検証します。

03

concurrency test

やさしく言うと

複数処理を意図的に同時進行させ、lockや一意制約の働きを確認します。

なぜ必要?

逐次実行ではlost update、deadlock、二重承認などの競合を再現できません。

Minibankでは

同一口座への複数送金、同一key、承認と否認などを別connectionから競合させる必要があります。

04

failure recovery test

やさしく言うと

処理途中で意図的に失敗させ、半端な記録が残らないかを確認します。

なぜ必要?

成功経路だけではtransactionとrollbackの保証を証明できません。

Minibankでは

仕訳や監査の途中で例外を起こし、業務記録・残高・台帳がすべて元に戻るかを検査対象にします。

05

状態機械test

やさしく言うと

各状態と許可された遷移、終了状態の再実行を確認します。

なぜ必要?

WHERE句で終了行が見えなくなると、not found・expired・failedを取り違えるからです。

Minibankでは

transferOtp suiteが誤入力1〜5回、6回目、再発行、期限、owner、consumedを区別します。

発展 — ADVANCEDもっと詳しく確認する最初の学習では読まなくても問題ありません。仕組みをもっと深く理解したい場合に開いてください。

現在のテスト構成

種類実装守る境界
DB / RPC RegressionJavaScript suite + Supabase CLI / clientconstraint、RLS、RPC、状態、冪等性、Ledger、権限
API Regressionscripts/regression/apiBearer認証、Zod、header、HTTP status、安全なerror body
Browser E2EPlaywright full-journey.spec.ts顧客・管理者の実画面からDB反映まで
Security / Privilegeprivilege.mjs、actorSeparation.mjsfunction ACL、table privilege、RLS、schema露出、actor兼務
Concurrency一部suiteとadhoc multi-session検証idempotency・unique・admin TOCTOU等。一律の負荷競合suiteではない
Unit通常の独立unit test runnerは未確認pure functionを細かく隔離する層は現状薄い

Regression:過去の安全契約を、変更後も守る

回帰testは新機能の確認だけではありません。KYC、口座、Ledger、振込、逆仕訳、admin、audit、reconciliation、権限といった既存契約を、新しいOTPやお気に入り追加後も壊していないか確かめます。Hosted DBへ実dataを作るためrunnerは並列安全ではないと明記され、suiteを直列に進めます。

  1. 01run-regression.mjsを開始
  2. 02固定actorとfixtureの前提をassert
  3. 03run.mjsがDB/RPC suiteを直列実行
  4. 04run-api.mjsがHTTP API suiteを実行
  5. 05全結果をDB / API / TOTALで集計
  6. 06失敗したcase IDとdetailから退行箇所を特定

API test:DBが安全でも、入口が壊れることを防ぐ

API testはRoute Handler、auth context、validation、Service、error変換を確認します。DBへ直接RPCを呼ぶtestとは違い、HTTP境界で利用者に何が見えるかを守ります。

確認防ぐ事故
認証なし・壊れたtoken未認証callerがAPIへ到達する
request bodyとIdempotency-Key不正型、桁あふれ、keyなし、別内容のkey再利用
SQLSTATE mappingDBの生message、constraint名、内部SQLがclientへ漏れる
他人のresourceIDを直接指定してownershipを突破する
Response契約Service変更でstatusや公開fieldが意図せず変わる

E2E:人が操作する一連の旅を確認する

Playwrightのfull-journey.spec.tsは、未認証redirect、customer login、KYC申請、admin承認、口座開設、freeze/unfreeze、customer/admin分離、logoutを実browserで確認します。振込arcは入力→DB解決済み名義の確認→demo OTP→完了→履歴を通り、残高不足では安全な日本語だけが表示されapplication shellが壊れないことを確認します。

E2Eで分かるE2Eだけでは分からない
画面・API・Auth・DBがつながる全functionのGRANT/REVOKEが正しいか
利用者に見える文言と導線あらゆる同時実行race
代表的な成功・失敗journeyDB ownerや全攻撃patternへの耐性
OTP後に履歴へ反映されるすべてのLedger不変条件の網羅

Security / Privilege test:見えない権限表を検査する

privilege.mjsはUIを使わずPostgreSQL catalogを読み、公開関数が存在するか、PUBLIC/anon/service_roleへ不要なEXECUTEがないか、authenticatedだけへ必要権限があるかを確認します。主要tableのwhole-table privilege、column privilege、RLS有効化、Data API公開schemaも別々に検査します。

actorSeparation.mjsはactive adminへのcustomer INSERTがAC001で拒否される方向と、customerをadminへ昇格しようとするとAM006で拒否される方向を試し、失敗後にcollision rowが残らないことも確認します。

Case 1:function overload競合をtestが発見

PostgreSQLではfunction名だけでなく引数型の並びもidentityです。引数signature変更は『同じ関数の更新』とは限りません。Migrationではpg_procやPostgREST経由の旧契約も確認する必要があります。

  1. 01execute_transfer()へdefault付き引数を追加
  2. 02CREATE OR REPLACE FUNCTIONを実行
  3. 03旧3引数版は別signatureなので残留
  4. 043 named argumentsは旧版と新版の両方に一致
  5. 05PostgRESTが選べずPGRST203
  6. 06Regressionで全旧callが失敗して発見
  7. 07旧3引数signatureを明示DROPし1候補へ戻す
旧: execute_transfer(text, bigint, uuid)
新: execute_transfer(text, bigint, uuid, text DEFAULT NULL, ... )
3引数call → 2候補 → PGRST203

Case 2:状態機械をWHERE句で消したバグ(#128以前のデモOTP実装)

DBに行は存在していても、WHERE条件で見えなくするとprogramからはnot foundに見えます。状態機械では『存在しない』『終了した』『今も有効』を別の意味として扱い、行を取得してから状態を判断します。transferOtp suiteは5回目と6回目の結果を固定します。

  1. 01OTPを5回誤入力
  2. 02challenge.status = failed
  3. 03旧queryはWHERE status = 'active'
  4. 04failed行が検索結果から消える
  5. 05『存在しない』分岐へ入りotp_expired
  6. 06最新challengeをstatus不問で取得
  7. 07取得後にfailed / expired / superseded / consumedを分岐

Case 3:生DBエラー漏洩をE2E更新中に発見

修正では内部診断まで捨てず、private tableのfailure_reasonにはraw messageを残します。外部境界にはcodeだけを渡すことで、運用者の調査能力と利用者への情報最小化を両立しています。

  1. 01OTPは正しいがexecute_transfer()が残高不足
  2. 02exceptionのmessage_textをfailure_reasonへ格納
  3. 03同じmessageをRPC responseへ返す
  4. 04clientへ内部詳細が出る経路になる
  5. 05RPC returnをfailure_code=SQLSTATEへ変更
  6. 06mapRpcError()がallowlist済み日本語へ変換
  7. 07E2Eでraw DB patternがbodyにないことを確認

Concurrency test:順番に2回では再現できない

同時実行testは複数sessionの処理時間を重ね、同じ古い状態を読ませます。Minibankには冪等性unique、口座row lock、決定順lock、admin caller row lockなどのDB対策と一部のmulti-session adhoc検証があります。

一方、adminQuorum suiteは真の同時disable競合を通常Regressionで実行しないと明記しています。全振込・KYC・admin操作のraceを再現する決定的なconcurrency suiteや、長時間の負荷・耐久testが完成済みとは記載しません。

scenario期待する結果
同一口座から残高を超える2送金lock後に片方が残高不足となりoverdraftしない
同一idempotency keyを同時送信実資金移動は1回、別内容ならconflict
OTP確認と再発行が競合request row lockでchallenge遷移を直列化
管理者無効化と管理RPCcaller row lock後の再認可でdisabled callerを拒否
最後のmanagerを同時変更quorumを0にしない。通常suiteでの完全競合再現は未実装

安定点5173876の検証記録

今回のRegressionではHosted環境にtest外のactive admin_managerが2人存在したため、『最後のmanager』を作る前提が成立せずADMIN-005/006は安全にSKIPされました。runner集計はskipを分母から除外し、実行された280 caseがPASSです。環境依存で危険な操作を無理に行わない設計もtest harnessの重要な性質です。

検査安定点固定時の記録このDocs作業での確認
Regression280 / 280 PASS2026-08-23に再実行し、DB/RPC 259/259 + API 21/21 = 280/280 PASS
Browser E2E11 / 11 PASSを2回連続2026-08-23に1回再実行し、11 / 11 PASS(1.7分)
TypeScript / Lint / BuildPASS専用worktreeで再実行しPASS
Hosted Migration全適用済みMigrationをread onlyで参照。DocsからDB変更しない

実際のMinibankでは

安定点の新suiteとしてtransferOtp、transferBeneficiaries、actorSeparationがDB/RPC runnerへ組み込まれ、privilege matrixにはrequest_transfer、confirm_transfer_otp、reissue_transfer_otp、admin_list_transfer_requestsが追加されています。お気に入りsuiteはowner分離、自己口座、重複、非owner更新・削除、last_used_at、audit、table ACLを確認します。

体験学習の続き · LEARNING LOOP

ここまで読めましたか?続けて確認してみましょう

テストの役割を読んだところで、教材内の参照元で実際のテスト記録を確認し、自分の言葉で説明してみます。

STEP 2〜5 / 5
  1. 02
    仕組みを確かめる — CONCEPT CHECK

    画面操作なしで、仕組みを確かめる

    この仕組みは、Mini Bankの画面には現れません。次のEvidenceとCode Trail、教材本文だけで理解を進めます。

  2. 03
    証拠を確認する — EVIDENCE

    何を見たか、1つずつ確かめる

    Evidence(判断の根拠となる記録)を確認したらチェックします。各項目の「何を見るか」を手がかりにしてください。

    画面を開けない項目は、Source Trail(教材の参照元)と教材の説明で確認できます。失敗を実際に起こす必要はありません。

  3. 04
    コードの処理経路を追う — CODE TRAIL

    コードを書く必要はありません

    ここでは「説明されている仕組みが、実際のMini Bankのどこに実装されているか」を確認します。まずは日本語の役割説明を読み、入力した依頼がどこへ渡るかをつかみましょう。コードの全文を読む必要はありません。

    基本の順序は、画面 → API Route(依頼を受ける窓口)→ TransferService(処理をつなぐサービス)→ DB / RPC(記録の保存場所と、その処理の呼び出し)です。まず最初の2つを見て、続くサービスは「もっと詳しく」で確認します。

    DBとAPIの回帰テストをまとめて実行するrunner。どの順序でテストが実行されるかを探します。

    scripts/regression/run-regression.mjs

    Regression test(変更後も約束が保たれるかの確認)。#128以前のデモOTPの誤入力・再発行・期限切れなどを検査する実装を確認します。v7のTOTP認可は対象外です。

    scripts/regression/suites/transferOtp.mjs
    教材の参照元を確認する — Source Trail ↓
  4. 05
    自分の言葉で説明する — EXPLAIN

    LEVEL 1 — やさしい説明(まずここだけ)

    なぜ「正常に動く」ことの確認だけでなく、失敗や攻撃を試すテストが必要なのでしょうか? 短い2文を目安に説明してください。

    日常語だけで完了できます。専門用語の数や正誤を自動採点するものではありません。

    ヒント1を見る

    「正常系」と「異常系」という2つの言葉を使って書いてみましょう。

    ヒント2を見る

    テストが実際に見つけたバグの例を、1つ挙げてみましょう。

    もっと詳しく:LEVEL 2 — 専門用語を使った説明(任意)

    DB regression test、concurrency test、failure recovery test、状態機械testの4つを使って、テストが守っている境界を説明してみましょう。

    挑戦する場合は、上の説明欄に追記できます。追加しなくても完了できます。

    説明の確認ポイント — Checkpoint
    • 正常系だけでなく異常系や同時実行も確認する必要があると説明できる
    • テストが実際にバグを発見した実績があると説明できる
予想5文字以上・証拠2項目・説明20文字以上で完了できます。

証拠の確認:0 / 2。LEVEL 2(専門用語での説明)は任意です。

入力はこのブラウザに保存されます。Academy教材の進捗とは別の学習メモです。

理解を確かめる — CHECK YOUR UNDERSTANDING

4問で理解チェック

答えを頭の中で考えてから、各項目を開いて確認してください。

Q1同じ送金を順番に2回呼べばconcurrency testですか?
答え

違います。別connectionから処理時間を重ね、同じ古い状態を読もうとする状況を作る必要があります。

Q2E2Eが通ればDB権限も安全ですか?
答え

断定できません。直接RPC、GRANT、RLSなどはDBレベルの負のテストが必要です。

Q3Hosted環境のテストで注意することは?
答え

実データを書き換える可能性があります。専用環境・専用データ・後片付け・実行権限を明確にします。

Q4280/280 PASSなら、すべての競合と負荷も安全ですか?
答え

断定できません。合格は実装済み280 caseの結果であり、広範なmulti-session競合、負荷、耐久、災害復旧には別のtestが必要です。

ここまで読めましたか?

完全に暗記する必要はありません。大切な考え方を説明できそうなら押してください。

教材の参照元 — SOURCE TRAIL

根拠と関連教材

CURRENT IMPLEMENTATIONは確認したrepository内の実装です。HISTORICAL IMPLEMENTATIONは導入・修正の履歴で、後続migrationによる再定義があり得ます。RELATED LESSONは教材への案内で、実装の一次Sourceではありません。ASSUMPTIONは仮定、UNKNOWNは未確認を表します。

確認基点: d02eae8914f39cbf06bfe422dc39c71886f82b3e / 2026-09-11。稼働環境への適用確認: UNKNOWN。パス実在の自動検査と、記述した技術的事実の確認は別です。

  • 現在の実装 — CURRENT IMPLEMENTATIONscripts/regression/run.mjsDB/RPC suite runner
  • 現在の実装 — CURRENT IMPLEMENTATIONscripts/regression/run-api.mjsAPI suite runner
  • 現在の実装 — CURRENT IMPLEMENTATIONscripts/regression/run-regression.mjsDBとAPIの回帰runner
  • 過去の実装 — HISTORICAL IMPLEMENTATIONscripts/regression/suites/transferOtp.mjs#128以前のデモOTP前提の回帰(OTP lifecycle・owner・冪等性・error境界)。v7のTOTP認可はscripts/mfa/intent-db.test.mjsが対象
  • 現在の実装 — CURRENT IMPLEMENTATIONscripts/regression/suites/transferBeneficiaries.mjs振込先owner・権限・audit
  • 現在の実装 — CURRENT IMPLEMENTATIONscripts/regression/suites/privilege.mjsfunction ACL・table privilege・RLS・schema
  • 現在の実装 — CURRENT IMPLEMENTATIONscripts/regression/suites/actorSeparation.mjscustomer/admin兼務の双方向guard
  • 過去の実装 — HISTORICAL IMPLEMENTATIONsupabase/migrations/20260823170002_drop_old_execute_transfer_overload.sqlCase 1の原因と修正
  • 過去の実装 — HISTORICAL IMPLEMENTATIONsupabase/migrations/20260823170003_fix_confirm_and_reissue_transfer_otp_bugs.sqlCase 2の原因と修正
  • 過去の実装 — HISTORICAL IMPLEMENTATIONsupabase/migrations/20260823170004_sanitize_confirm_transfer_otp_failure_reason.sqlCase 3の原因と修正
  • 現在の実装 — CURRENT IMPLEMENTATIONscripts/regression/adhoc/verify-admin-toctou-fix.mjs限定的なmulti-session検証
  • 現在の実装 — CURRENT IMPLEMENTATIONe2e/full-journey.spec.tsPlaywright E2E