コンテンツにスキップ

7-2. 演習問題


1-1. TDD(テスト駆動開発)のサイクルの順序として最も適切なのはどれか。

  • A. Red → Green → Refactor
  • B. Green → Red → Refactor
  • C. Refactor → Green → Red
解答と解説

正解:A. Red → Green → Refactor

  • A が正しい。TDD の基本サイクルは「失敗するテストを書く → 通す実装をする → 整える」の順である。
  • B は誤り。Green から始まるということはすでに実装があることになり、TDD のサイクルではない。
  • C は誤り。先にリファクタリングをすることはできない。

このサイクルを繰り返すことで、小さな単位で設計と実装を進めていく。

1-2. TDD で「Red」の状態とは何か。

  • A. テストが失敗している状態(まだ実装がない)
  • B. テストが全て通っている状態
  • C. コードを整理し終えた状態
解答と解説

正解:A. テストが失敗している状態(まだ実装がない)

  • A が正しい。Red はテストが失敗している状態を指す。実装前なので当然失敗する。
  • B は誤り。全て通っているのは Green の状態である。
  • C は誤り。コードを整え終わった状態は Refactor 後の状態である。

Red を出発点にすることで、「最初にテストが本当に失敗しているか」を確認できる。

1-3. TDD で「テストを先に書く」ことの主なメリットとして最も適切なのはどれか。

  • A. 何を作るかを先に明確にし、設計品質を高めやすい
  • B. コードを書かなくてもよくなる
  • C. テストが全く失敗しなくなる
解答と解説

正解:A. 何を作るかを先に明確にし、設計品質を高めやすい

  • A が正しい。テストを先に書くことで、作るべき動作を先に定義し、設計の改善も促される。
  • B は誤り。テストを書いてもコードを書く必要がなくなるわけではない。
  • C は誤り。テストが失敗しなくなるということはなく、意図的に失敗させることがスタートである。

「先にテストを書く」とは「先に仕様を決める」ことと同義でもある。

1-4. コードレビューでセキュリティの観点として最も適切なのはどれか。

  • A. SQLに入力を直接結合しないか確認する
  • B. コメントの行数が100行以上あるか確認する
  • C. ファイル名に大文字が含まれていないか確認する
解答と解説

正解:A. SQLに入力を直接結合しないか確認する

  • A が正しい。SQL への直接連結は SQLi につながる危険があり、セキュリティ観点での典型的な確認事項である。
  • B は誤り。コメント行数はセキュリティとは無関係である。
  • C は誤り。ファイル名の大文字は通常セキュリティの問題ではない。

コードレビューのセキュリティ観点では、脆弱性につながる実装パターンを探す。

1-5. レビューコメントを受けたときの対応として最も適切なのはどれか。

  • A. 指摘の意図を理解した上で修正する
  • B. 指摘があったコードは全て削除する
  • C. レビュアーの言葉をそのままコードにコピーする
解答と解説

正解:A. 指摘の意図を理解した上で修正する

  • A が正しい。なぜ指摘されたのかを理解することで、同じ問題を再発させにくくなる。
  • B は誤り。全削除はコードを壊す危険がある。
  • C は誤り。レビュアーの意見を正しく解釈して実装するのは開発者の判断が必要である。

レビューはコードの品質を高めるための対話であり、指摘を理解することが学びにつながる。

1-6. テストがあることでリファクタリングが安心してできる理由として最も適切なのはどれか。

  • A. コードを変えても動作が変わっていないことをテストで確認できるから
  • B. テストがあるとコードが自動的に最適化されるから
  • C. テストを実行するとバグが全てなくなるから
解答と解説

正解:A. コードを変えても動作が変わっていないことをテストで確認できるから

  • A が正しい。テストがあるとリファクタリング後に動作が変わっていないかを確認できる。
  • B は誤り。テストはコードを自動最適化しない。
  • C は誤り。テストを実行してもバグが全て消えるわけではない。

「テストを守りながらコードを変える」という安心感が、設計改善の機会を増やす。


次の文章の空欄を埋めてください。

  1. テストを先に書き、Red → Green → Refactor のサイクルで進む手法を (   ) という。
  2. TDD の「Refactor」とは、テストが通った状態を保ちながら (   ) を改善することである。
  3. テストしやすいコードは、依存が少なく (   ) になりやすい。
  4. コードレビューでは、バグ・セキュリティ・読みやすさ・影響範囲・(   ) を確認する。
  5. レビューコメントの種類のうち、動作が壊れる・危険な実装の指摘は (   ) 修正である。
  6. テストが書かれていると、レビュアーがコードの (   ) をテストから読み取れる。
解答欄
解答と解説
  1. TDD(テスト駆動開発)

Red → Green → Refactor のサイクルでコードを積み上げていく手法である。

  1. 内部構造(コードの設計)

リファクタリングは「動作を変えずに構造を改善すること」であり、テストが変化しないことを確認しながら行う。

  1. 疎結合

テストしやすいコードは外部依存が少なく、変更しやすい構造になりやすい。

  1. テスト

コードレビューでは、テストが書かれているか・網羅的かも確認する重要な観点の一つである。

  1. 必須

動作を壊す・危険な実装への指摘は、任意の提案ではなく必須の修正に分類される。

  1. 仕様(意図)

テストがあると「この関数がどう動くべきか」をレビュアーが読み取れるため、レビューの議論が的確になりやすい。


3-1. TDD の Red → Green → Refactor サイクルを、「本のタイトルのバリデーション関数を作る」という例で説明してください。

解答欄
解答と解説

3-1. TDD サイクルの例

Red: 「タイトルが空のときは例外を投げるべき」というテストを書く。まだ addBook がないので失敗する。 Green: テストを通すために addBook(books, title) を実装する。空なら例外、そうでなければ追加して返す。 Refactor: 変数名やエラーメッセージを整理し、null と空文字列の条件を明確に分ける。テストは引き続き通ることを確認する。

3-2. 「テストしやすいコードは設計が良い」と言われる理由を説明してください。

解答欄
解答と解説

3-2. テストしやすいコードは設計が良い理由

テストを書こうとすると「この関数を単体で呼べるか」「外部依存を差し替えられるか」を考えることになる。 その結果、自然と依存関係が明確になり、疎結合な設計になりやすい。 逆に「テストが書きにくいコード」は、複数の責務が混在しているか、外部依存がハードコードされていることが多い。

3-3. コードレビューでレビューを受ける側が、フィードバックをどのように受け取るべきかを説明してください。

解答欄
解答と解説

3-3. レビューのフィードバックの受け取り方

レビューコメントはコードへの意見であり、人格への批判ではない。 フィードバックを受けたときは「なぜそう指摘されたのか」を理解することが重要で、 意図が分からなければレビュアーに確認する。修正は「言われたから直す」ではなく「理解して直す」姿勢が学びになる。

3-4. テストとコードレビューを組み合わせると、なぜ品質が向上しやすいのかを説明してください。

解答欄
解答と解説

3-4. テストとレビューを組み合わせる効果

テストがあると、レビュアーが「どう動くべきか」をテストから読み取れるため、意図の確認が早くなる。 また、レビュー中に「このテストケースが抜けている」という指摘ができるようになる。 テストが品質の土台を作り、レビューがそこでは気づきにくい設計や安全性の問題を補う関係になる。


TDD のサイクルをブラウザ上で体験してみましょう。

書籍管理の addBook 関数を、Red → Green → Refactor の順で実装します。

① Red:テストを先に書く(実行するとまず失敗する状態)

下のコードはそのまま実行できます。まず動かして「Red」の状態を確認してください。

実行プレイグラウンド
編集内容は自動保存されます / Ctrl+Enter でも実行できます
出力
「実行」を押すと、ここに結果が表示されます。

「実行」を押すと次のようなエラーが出れば成功(これが Red の状態):

❌ FAIL または ReferenceError: addBook is not defined

② Green:テストが通る最小限の実装を書く

次のコードを実行して、テストを全て通してください。

実行プレイグラウンド
編集内容は自動保存されます / Ctrl+Enter でも実行できます
出力
「実行」を押すと、ここに結果が表示されます。

次のような表示が出れば成功(これが Green の状態):

✅ PASS: 本が1冊追加される
✅ PASS: タイトルが空のときは例外を投げる (threw: title is required)
✅ PASS: タイトルがnullのときは例外を投げる (threw: title is required)

③ Refactor:振る舞いを変えずにコードを整える

Refactor は「テストを通ったまま、内部構造だけを改善する」ステップである。新しい機能を追加してはいけない。

次のコードは Green とテストの内容・結果が全く同じままで、長い条件式を変数へ切り出して整理している。 実行して、引き続き全テストが通ることを確認してください。

実行プレイグラウンド
編集内容は自動保存されます / Ctrl+Enter でも実行できます
出力
「実行」を押すと、ここに結果が表示されます。

次のような表示が出れば成功(Green と同じ結果):

✅ PASS: 本が1冊追加される
✅ PASS: タイトルが空のときは例外を投げる (threw: title is required)
✅ PASS: タイトルがnullのときは例外を投げる (threw: title is required)

④ 次のサイクル:前後の空白除去を追加する(Red → Green)

新しい仕様(前後の空白を除去する)を追加するときは、また Red から始める。 まず失敗するテストを追加し、次にそれを通す実装を書く。

実行プレイグラウンド
編集内容は自動保存されます / Ctrl+Enter でも実行できます
出力
「実行」を押すと、ここに結果が表示されます。

次のような表示が出れば成功:

✅ PASS: 本が1冊追加される
✅ PASS: 前後の空白は除去される
✅ PASS: タイトルが空のときは例外を投げる (threw: title is required)
✅ PASS: 空白のみのタイトルは例外を投げる (threw: title is required)
✅ PASS: タイトルがnullのときは例外を投げる (threw: title is required)
解答と解説

① Red の状態

addBook が定義されていないため、ReferenceError が発生する。 これが TDD のスタート地点であり、「テストが通るべき実装がまだ存在しない」状態である。 意図的に失敗させることで、「このテストが実際に動いているか」を確認できる。


② Green の状態

テストを通す最小限の実装として、空チェックと push を書いた。 !title は空文字列・null・undefined・0 などに対して true になるため、この段階では簡潔に書ける。 全テストが ✅ PASS になれば、動作の基本が確認できた状態である。


③ Refactor の変化点

!title という条件を isMissing という名前の変数に切り出し、意図を読みやすくした。 この段階では trim() や新しいバリデーションは追加していない。 テストの内容・期待値・合格数はすべて Green と同じである。

Refactor の本質は「動作を変えずに内部構造を改善すること」であり、 テストが通り続けることがそれを保証している。


④ 次のサイクル

trim() による前後空白除去は 新しい仕様 なので、また Red から始めた。 まず失敗する新しいテスト("前後の空白は除去される""空白のみのタイトルは例外を投げる")を追加し、 それを通す実装(title.trim() の組み込み)を書いて Green にする。

つまり TDD のサイクルは終わりではなく、新しい仕様が来るたびに繰り返す。


TDD の流れをまとめると

Red: テストを書く(失敗することを確認する)
Green: 最小限の実装で通す
Refactor: 動作を変えずに設計を整える
次のサイクルへ(新しい仕様があれば繰り返す)

この繰り返しにより、小さく・確認しながらコードを積み上げていく。


次のコードを読んで、レビューコメントを書いてください。

// 書籍一覧APIのハンドラ(Node.js + Express 想定)
app.get("/books/:id", (req, res) => {
const id = req.params.id;
const result = db.query("SELECT * FROM books WHERE id = " + id);
res.json(result);
});
app.delete("/books/:id", (req, res) => {
const id = req.params.id;
db.query("DELETE FROM books WHERE id = " + id);
res.json({ ok: true });
});

5-1. セキュリティ上の問題点を指摘してください。また、なぜ危険なのかを説明してください。

解答欄
解答と解説

5-1. セキュリティ問題:SQL Injection

req.params.id を直接 SQL 文字列に連結しているため、SQL Injection の危険がある。

たとえば id1 OR 1=1 のような文字列が来ると、 SELECT * FROM books WHERE id = 1 OR 1=1 となり全件が返る。 DELETE 側では全件削除につながる危険もある。

なぜ危険か: URL パラメータは攻撃者が自由に変えられる外部入力であり、 それをそのまま SQL に混ぜると、命令の構造を書き換えられてしまう。

5-2. DELETE エンドポイントには、セキュリティ以外にも見落とされやすい問題があります。何か指摘してください。

解答欄
解答と解説

5-2. DELETE エンドポイントのその他の問題

認可チェックがない。ログイン済みかどうか・削除権限があるかを確認せずに削除を実行している。 つまり誰でも任意の本を削除できてしまう状態である。

また、存在しない id を渡したときのエラー処理がない。 DB のエラーがそのままレスポンスに漏れる恐れもある。

指摘分類の例: SQL 連結はバグ修正必須・セキュリティ、認可チェックなしもバグ修正必須。

5-3. このコードをレビューする場合、どのような修正を提案しますか。修正後のコードのイメージを書いてください。

解答欄
解答と解説

5-3. 修正後のコードイメージ

// GET: パラメータ化クエリ・認証確認
app.get("/books/:id", authenticate, (req, res) => {
const id = req.params.id;
const result = db.query("SELECT * FROM books WHERE id = ?", [id]);
if (!result) return res.status(404).json({ error: "not found" });
res.json(result);
});
// DELETE: パラメータ化クエリ・認可確認
app.delete("/books/:id", authenticate, authorize("admin"), (req, res) => {
const id = req.params.id;
db.query("DELETE FROM books WHERE id = ?", [id]);
res.json({ ok: true });
});

主な変更点:

  • SQL の文字列連結 → プレースホルダ ? でパラメータ化
  • 認証ミドルウェア authenticate の追加
  • DELETE には認可ミドルウェア authorize("admin") を追加