TDDについて考えたこと。

さて、TDDBCに行って来て、自分なりに考察していることがるので、まとめてみようと思う。

バグはテストをすり抜ける

「バグはテストをすり抜ける」例を考えてみた。

TDDBCに行った時の課題に郵便番号のバリデーションがあった。そのときの仕様がこちら

3文字が数字ならOK 123
7文字が数字ならOK 1231234
8文字のでは 123-1234ならOK
それ以外がNG (空文字、12a, 1234, 12345678など)
(途中で仕様が追加されたが、最初の仕様を使って考える。)

さて、この課題に対して、こんなコードを書いたとしよう。
(http://nobkz.hatenadiary.jp/ よりコードを改変)

# バグあり
def validation? zip_code
  if [3,7].map{|i| zip_code.length == i}.any?
    return is_numbers? zip_code
  end
  if zip_code[3] == "-"
     validation?(cut_fourth zip_code)
  end
  false
end

def is_numbers? zip_code
  zip_code.split("").map{|c| is_a_number? c}.all?
end

def is_a_number? s
  (0..9).map{|i| i.to_s}.map{|i| i == s}.any? 
end

def cut_fourth zip_code
  zip_code[0..2] + zip_code[4..8]
end

そして、こんな感じのテストコードがあったとしよう。このテストは通ります。

describe "zip code validation" do
  shared_examples_for :validation do |bool ,zip_codes|
    zip_codes.each do |zip_code| 
      describe "validation? '#{zip_code}'" do
        if bool
          it{ validation?(zip_code).should be_true}
        else
          it{ validation?(zip_code).should be_false}
        end
      end
    end
  end
  
  context "About 3-length string" do
    it_should_behave_like :validation, true, ["000","111"]
    it_should_behave_like :validation, false, ["abc","1 1","---"]
  end 

  context "About 7-length string" do
    it_should_behave_like :validation, true, ["1234567"]
    it_should_behave_like :validation, false, ["aaa1234","123-123"]
  end

  context "About 8-length string" do
    it_should_behave_like :validation, true, ["111-1111"]
    it_should_behave_like :validation, false, ["12345678","12-34567"]
  end

  context "About other-length string" do 
    it_should_behave_like :validation, false, ["","1114","77741","77712345"]
  end
end

テストはキチンと通っているし、いい感じにプログラムは動いているように見える。だが、(おそらく多くの人は気づいているだろうが)このプログラムは不適切だ。

どこがいけないのだろうか?

そう、4文字の長さの文字列の最後に"-"がつくと、このコードはtrueを返してしまうのだ。
試しに"123-"を引数に取ってみよう。

validation? "123-" #=> true

"123-"はfalseを返してほしいはずだ。そう、この場合でいえば"123-"はテストをすり抜けるのだ。

考察

きちんとテストを書くこと

当然なのだが、テストカバレッジは大事。
この場合「validation? "123-"」というテストを書けるかどうかが問題なのだ。

なにが言いたいのかって言えば、ちゃんとテストを勉強しようって話。

TDDのテストは、ある程度正しく動くことを保証するもの

「TDDは”動くコード”を触るなのアンチテーゼ」
「TDDのテストを書く理由として、正しく動くことを保証して、安心してリファクタリングを行うこと」

って、どこかの誰かさんが言っていたのか、何処かの本に書いてあったか忘れたが、聞いた事あるんだ。 だけど、もしかしたら、そのリファクタリングによって上記と似たような状況が起こるかもしれない。(というか実際に起こった。)

「テストに依って保証された」範囲で、プログラムは動くことを自覚した上で、リファクタリングをする事が大事。

TDDは品質を保証しない。

他の何らかの方法で品質を担保すべき。