読者です 読者をやめる 読者になる 読者になる

新人SEの学習記録

14年度入社SEの学習記録用に始めたブログです。気づけば社会人3年目に突入。

学習記録:リーダブルコード

[学習記録] リーダブルコード

第Ⅳ部:選抜テーマ

14章:テストを読みやすくする

  • 他のプログラマが安心してテストの追加や変更ができるようにする
    • テストコードが大きくて恐ろしいものだったら・・・
    • 本物のコードを修正するのを恐れる
    • 新しいコードを書いた時にテストを追加しなくなる
このテストのどこがダメなのか?
  • 検索結果のスコアをソートしてフィルタする関数のテスト
void Test1() {
    vector<ScoredDocument> docs;
    docs.resize(5);
    docs[0].url = "http://example.com";
    docs[0].score = -5.0;
    docs[1].url = "http://example.com";
    docs[1].score = 1;
    docs[2].url = "http://example.com";
    docs[2].score = 4;
    // 残りdocs[3]〜[4]の設定
    
    SortAndFilterDocs(&docs);

    assert(docs.size() == 3);
    assert(docs[0].score == 4);
    assert(docs[1].score == 3.0);
    assert(docs[2].score == 1);
}
  • 少なくとも8つの問題がある。以下で修正していく
テストを読みやすくする
  • 大切でない詳細はユーザから隠し、大切な詳細は目立たせる
    • 上の例は、どうでもいいdocsの設定が一番目立っている
    • ヘルパ関数MakeScoreDocを作る
void Test1() {
    vector<ScoredDocument> docs;
    docs.resize(5);
    MakeScoreDoc(&docs[0], -5.0, "http://example.com");
    MakeScoreDoc(&docs[1], 1, "http://example.com");
    MakeScoreDoc(&docs[2], 4, "http://example.com");
    MakeScoreDoc(&docs[3], -99998.7, "http://example.com");
    // 以下省略...
 }
  • これでも不十分
    • http://example.comは目障りだし、docs.resizeや&docs[0]なんでのも邪魔
    • ヘルパ関数を修正し、AddScoreDoc()にする
void Test1() {
    vector<ScoredDocument> docs;
    AddScoreDoc(docs, -5.0);
    AddScoreDoc(docs, 1);
    AddScoreDoc(docs, 4);
    // 以下省略...
}
  • だいぶよくなったが、まだまだ楽に読み書きはできない
    • 新しい文書の一覧をテストしようと思ったら、大量のコピペが必要になる
    • どうすればよいか?
最小のテストを作る
  • このテストが何をしようとしているのか簡単な言葉で説明する
    • 文書のスコアは[-5, 1, 4, -99998.7, 3]である。
    • SortAndFilterDocs()を呼び出した後のスコアは[4, 3, 1]である。
    • スコアはこの順番である必要がある。
  • 上の文書からテストコードを作ると以下のようになる
CheckScoresBeforeAfter("-5, 1, 4, -9998.7, 3", "4, 3, 1");
  • テストの本質を一行にまとめることができた!
独自のミニ言語を実装する
  • 最近のC++では配列リテラルをそのまま引数として渡せる
CheckScoresBeforeAfter({-5, 1, 4, -9998.7, 3}, {4, 3, 1});
  • 昔はこれができなかったので、カンマ区切りの文字列をvectorに変換するメソッドが必要だった
    • そのような独自ミニ言語を実装することで、少ない領域で多くの情報を表現できる
    • assertのエラーメッセージが気に入らなければ、自分で作ってみるなど
テストの適切な入力値を選択する
  • コードを完全にテストする最も単純な入力値の組み合わせを選択しなければならない
    • 今使っている値はでたらめ
    • 不足の例:"1, 2, 3"を入力値にした場合、マイナスのスコアをフィルタする動作が確認できない
    • 過剰な例:"1234014, -1082342, 158966, 445623, -234451...."
  • テストには最もキレイで単純な値を選ぶ。
    • テストしたいのは、「正の値は降順に並ぶ」、「負の値はフィルタされる」
    • "1, 2, -1, 3"など
  • 完璧な入力値を1つ作るのではなく、小さなテストを複数作る方がわかりやすい
    • ソートのテスト、マイナスの削除のテスト、重複が許可されるテスト、空白が許可されるテスト
テストの機能に名前をつける
  • Test1()などの名前は避けるべき
    • テストコードを読む人が、以下のことがすぐにわかるような命名を
    • テストするクラス(あれば)、関数
    • テストする状況やバグ
  • さっきの例。まずはTest_という接頭辞を付けて情報をひとまとめに
    • Test_SortAndFilterDocs()
    • 更に分割する場合、状況名を付ける:Test_SortAndFilterDocs_BasicSorting()
このテストのどこがダメだったのか?
void Test1() {
    vector<ScoredDocument> docs;
    docs.resize(5);
    docs[0].url = "http://example.com";
    docs[0].score = -5.0;
    docs[1].url = "http://example.com";
    docs[1].score = 1;
    docs[2].url = "http://example.com";
    docs[2].score = 4;
    // 残りdocs[3]〜[4]の設定
    
    SortAndFilterDocs(&docs);

    assert(docs.size() == 3);
    assert(docs[0].score == 4);
    assert(docs[1].score == 3.0);
    assert(docs[2].score == 1);
}
  1. どうでもいいことが大量に書かれている。
  2. テストが簡単に追加できない。
  3. 失敗メッセージが役に立たない。
  4. 一度にすべてのことをテストしようとしている。
  5. 入力値が単純でない。(-9998.7??)
  6. 入力値が完全でない。(スコアが0の場合は?)
  7. 極端な入力値をテストしていない。(空のベクタ、スコアの重複、巨大なベクタなど)
  8. Test1()という意味のない名前。
テストに優しい開発
  • テストしやすいコード
    • クラスが小さい。内部状態を持たない。
    • クラスや関数が一つのことをしている。
    • 高度に疎結合化されている。
    • 関数は単純でインタフェースが明確である。