by shigemk2

当面は技術的なことしか書かない

リーダブルコード 14 テストと読みやすさ

前回
リーダブルコード 13 短いコードを書く - by shigemk2

たとえばこのようなテストコードがあったとする。

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].url = "http://example.com";
  docs[3].score = -99998.7;
  docs[4].url = "http://example.com";
  docs[4].score = 3.0;

  SortAndFilterDocs(&docs);

  assert(docs.size() == 3);
  assert(docs[0].score == 4);
  assert(docs[1].score == 3.0);
  assert(docs[2].score == 1);
}

このコードには8つの問題があるという。

大切ではない詳細はユーザから隠し、大切な詳細は目立つようにする

ここではvectorというテストの本質ではない設定が
一番目立ってしまっている。
なぜならvectorとかurl score docs[] とかはC++
オブジェクトを設定する詳細であって、テストの本質じゃない。
そのためには、詳細を隠蔽するヘルパー関数を作成してみる。

void AddScoredDoc(vector<ScoredDocument>& docs, double score) {
  ScoredDocument sd;
  sd.score = score;
  sd.url = "http://example.com";
  docs.push_back(sd);
}

こんなふうにして、詳細を隠蔽してしまえば、テストコードも見やすくなるでせう。

void Test1() {
  vector<ScoredDocument> docs;
  AddScoreDocs(docs, -5.0);
  AddScoreDocs(docs, 1);
  ..
}

最小のテストを作る

テストは簡潔にするべきである。
テストの本質は「こういう状況と入力から、こういう振舞いと出力を期待するレベルまで要約することである。
そしてテストは1行でまとめられることが多い。

CheckScoresBeforeAfter("-5, 1, 4, -99998.7, 3", "4, 3, 1");

独自のミニ言語を実装する

独自のミニ言語を定義すれば、小さな領域で多くの情報を表現できる。
printfや正規表現ライブラリなども、文字列で別の意味を表現できるようになる。

void CheckScoresBeforeAfter(string input, string expected_output) {
  vector<ScoredDocument> docs = ScoredDocsFromString(input);
  SortAndFilterDocs(&docs);
  string output = ScoredDocsToString(docs);
  assert(output == expected_output);
}

エラーメッセージを読みやすくする

もし使えるのであれば、便利なアサーションメソッドを使うべきである。
Boost C++ ライブラリを使うとか。

BOOST_REQUIRE_EQUAL(output, expected_output)

テストの適切な入力値を選択する

コードを完全にテストする最も単純な入力値の組み合わせを選択しなければならない。
つまり、テストの値は、簡単に読めるような単純なものでなければならない。

CheckScoreBeforeAfter("1, 2, -1, 3", "3, 2, 1");

テストの機能に名前をつける

テストの関数名はTest1となっているが、これだと何のテストをしているのか分からない。
テストの内容を表した名前をつけるべきである。テストコードを読む人が、以下のことを
すぐに理解できるものがいい。

  • テストするクラス
  • テストする関数
  • テストする状況やバグ
void Test_SortAndFilterDocs_BasicSorting() {
  ...
}

など。

テストの書き方について、冒頭のサンプルのどこがだめだったのかというと、

  1. このテストにはどうでもいいことがたくさん書かれている
  2. テストが簡単に追加できない
  3. 失敗メッセージが役に立たない
  4. 一度にすべてのことをテストしようとしている
  5. テストの入力値が単純ではない。
  6. テストの入力値が不完全(0がない)
  7. 極端な入力値を使ってテストしていない(空のベクタ、巨大なベクタなど)
  8. Test1という意味のない名前を使っている。

テストに優しい設計をすれば、振舞いごとにうまく分割されていて、自然にコードが
構成されていく。テストしやすいように設計するようになる。