Object.create(null)

TypeError: Cannot convert object to primitive value

テストの説明に安易に「正しく」とか書かない

みなさんテストは書いていますよね. 書いていなければふりだしに戻る.

例えば関数 add に対して, 以下のようなテストコードがあるとします.

describe("add", () => {
  it("正しく計算できる", () => {
    expect(add(1, 2)).toBe(3);
  });
});

よさそうですね? もしよくないと思うのであればここから下は読まなくても大丈夫だと思います.

続いて関数名を変えただけのこちらをどうぞ.

describe("sub", () => {
  it("正しく計算できる", () => {
    expect(sub(1, 2)).toBe(3);
  });
});

なんだか明らかに間違っている気がします.

もしこのテストが通過してしまったとき我々はどうすればよいのでしょうか. 考えられるパターンは 2 つあります.

  • 実装もテストも正しい*1 (我々の間違っているという認識が間違っている)
  • 実装もテストも間違っている (我々の間違っているという認識は正しい)

ここで我々は関数名 sub やコメント, ドキュメントなどから実装とテストコードが明らかに間違っていると判断して修正できるわけです. よかったですね.

しかし現実には必ずしもそういったことができるとは限りません. 関数名から計算や処理の内容が明らかではなく, かつコメントやドキュメントも十分でないこともあります.

describe("calc", () => {
  it("正しく計算できる", () => {
    expect(calc(1, 2)).toBe(3);
  });
});

さてこのテストが通過していたとして, 以下のどちらか判断することは可能でしょうか?

  • 実装もテストも正しい
  • 実装もテストも間違っている

情報が不足していてわかりませんよね. 私はよくこういうのを見かけては, 何が "正しい" のかわからず泣いています.

こうならないために, もちろん関数名を具体的にしましょうとか関数に対してコメントやドキュメントを残しましょうといったことも大切なのですが, テストの説明文 (ここでは it の第一引数) の書き方を変えるだけでも十分に情報を与えることができるはずです. 「正しく計算できる」というのは何かを言っているようで実は何も説明していないんですよね.

テストの説明文は具体的に, 実装が何をすべきで, テストコードが実際にそれを確認しているということが明白に読み取れるようなものにしましょう.

describe("calc", () => {
  it("足し算を行う", () => {
    expect(calc(1, 2)).toBe(3);
  });
});

こうするだけで, calc は足し算を行う関数で, このテストコードはそれに合っていて, このテストが通過していれば実装もきっと正しいであろうということが読み取れるようになりました. あるいはこのテストコードを見て, 実装は正しいのだろうか? 実は中身は bitwise OR なのでは? といった疑問からテストの改善や実装の修正につなげることもできるでしょう.

コードに対する通常のコメントについては必ずなんらかの意図を持って書かれるため「正しく計算できる」のような内容になることはまずないと思うのですが, テストの説明についてはフレームワークによっては必須になっているためか, あまり内容について意識しないまま書かれてしまうことが多いように思います. 基本的には通常のコメントと同じで, 読者にとって意味のあることを書きましょう.

ちなみにここまでは実装の後にテストを書いている, あるいはそのようにして書かれたテストコードを読んでいるという想定でしたが, TDD のようなテストファーストな開発手法にも思いを馳せてみると, ここではテストコードが "正しい" ことが何であるかを定義すべきであるはずなので, やはり「正しく計算できる」といった説明には何の意味もない (自明あるいは循環定義) ことになります. とはいえテストを先に書いていると何をテストすべきかが意識の中心にあるはずなので, あまりこういった中身のないことは書かなさそうな気もしますね.

知らんけど.

*1:もちろんテストを通ったからといって実装が絶対に正しいとは言えないので, ここではきっと正しいくらいの意味合いです