テストコードの重複の基準について

最近テストコードを書く機会が増えテストコードの重複を許すかどうかの基準に少し悩んだりします。

テストコードのこの部分は重複しているから共通化してあげようとしたらそれはベタ打ちのままで良いよなんてこともあります。基準が人それぞれだったりするのです。

今回は、私のような悩みを持つ方に向けて、その基準についてまとめたのでみていってください。

テストコード重複

前提として、テストコードの目的は定義されたアプリケーションの仕様を満たしている状態を保証することとします。なのでテストコードは読みやすく、バグを検出するものになっていることが重要ですね。

よって開発では上記を満たすようにテストコードを書こうとするのですが、重複を許してテストコードを書いていくと別の問題が出てきます、それがメンテナンスコストの増大です。

重複した分だけ変更箇所が増えるのでメンテナンスコストが増大し、悪い方向へと向かっていきます。であれば、重複を排除しよう、と思うのですが、ハードコーディングした方が読みやすい場合が時としてあります。

このように重複を許すかどうかで享受できるメリットが変わるので意見が割れがちです。

このようなことは長年議論されており、歴史みたいになっていてとても面白いです。ここでそれぞれの意見を2つピックアップしたので見ていきましょう。

重複あり派の意見

そのテストコードのブロックだけ見れば、ある程度何をやっているかわかるという単純な書かれ方の方が良い。

石井勝さん

突然、人のテストを見たときに何を書いているのこのテストは?って説明を求めるところから始めるよりかは、わかるテストコードを見られることに魅力を感じています。意図もわかるコードを見せてほしいと。

MOZUさん

重複なし派の意見

テストコードはDRYを捨ててハードコーディングする方が良いという意見は15年前から定期的に出てきて、一時期はその方が良いとも言われていたが、後に望ましくなかったことがわかる。DRYを捨てて重複が増えたテストコードはメンテナンスコストの増大を招き、 多くの現場を苦しめたからだ。

Wada Takutoさん

テストコードも実コードである。 つまり、テストコードも実コードと同じ厳しい基準であたり、テストコードにも重複はあってはならない。

Dave ThomasとAndy Huntさん

その他にも多くの意見がありますが、共通する主張は大体こんな感じです。

●重複あり派の主張

読みにくい・理解するのに時間がかかるテストコードは、テストコードの役割を果たせなくなってくるので、だったらハードコーディングして読みやすさを重視した方が良いのではないか。

→テストコードの役割を果たすことに重きをおいている。(テストコードの役割は仕様が簡単に把握できたりバグを検知できたりすることだと思います。)

●重複なし派の主張

重複を許してしまうとメンテナンスコストが増大し、現場を苦しめる。またテストコードもコードなのだから重複すべきではないのだ。

→コストを問題視している。コストが増大すればプロジェクトの成長を妨げる。

どっち派になるかは結構現場の経験とかにもよりそうですよね。それでは、有識者の中でも意見が割れることがわかった上で自分がいいなと思う方を選びましょう。

あなたはどちらのテストコードが好みですか。

テストコードのサンプル

Code 1

				
					describe Cloth do
	describe '#half_price' do
		it '半額の値段を計算する' do
			cloth = Cloth.new('RSpec Tシャツ', 1000)
			expect(cloth.half_price).to eq 500
			
			cloth = Cloth.new('RSpec Tシャツ', 2000)
			expect(cloth.half_price).to eq 1000
			
			cloth = Cloth.new('RSpec Tシャツ', 999)
			expect(cloth.half_price).to eq 499
		end
	end
end
				
			
Code 2
				
					describe Cloth do
	describe '#half_price' do
		it '半額の値段を計算する' do
			cloth = Cloth.new('RSpec Tシャツ', 1000)
			expect(cloth.half_price).to eq cloth.price / 2
			
			cloth = Cloth.new('RSpec Tシャツ', 2000)
			expect(cloth.half_price).to eq cloth.price / 2
			
			# 端数が出る場合の半額はいくら??
			cloth = Cloth.new('RSpec Tシャツ', 999)
			expect(cloth.half_price).to eq cloth.price / 2
		end
	end
end
				
			

Code1の特徴は、
・バグを検出できる
・そのブロック内でやっていることがわかる
と、テストコードの役割に沿っていることがわかるが、重複は出てしまう。

一方、Code2の特徴は、
・ハードコーディングを共通化しているためメンテナンスコストを下げられるが、テストコード以外のファイルの参照が必要になる。
と、それぞれメリット・デメリットがあります。

私はテストコードの役割を果たす方が、ややメンテナンスコストの削減よりも優先してもいいかなと感じるので、Code1の方が好みです。

皆さんはどう感じましたでしょうか。 こういうことを意識するとテストコードを書くのも見るのも結構楽しくなりますよね。

(※ちなみに重複の排除の例としてCode2を用いましたが、Code2はバグを含んでいます。しかしそれを検知できずにテストを通してしまっているのです。よって共通化はメンテナンスコストを下げるかもしれませんが、共通化を行う際(特に数値が関わるロジックなど)には、バグを生むかどうか見極め慎重に行う必要があります。)

まとめ

テストコードの重複や重複の排除を行う際には以下の2点を意識しようと感じた。

・テストコードの役割を果たしているか。
・メンテナンスコストを生んでいないか。

現場によって良いテストコードの基準は違うためこれが正解というものはないけれど自分の基準を持っておくことが大事だと感じる。

何でこのようにテストを書いたの?と聞かれたときに、自分はこういう基準で〜と返せた方が建設的な議論ができるからだ。

テストコードを書く際はこれらのことを意識していこう。

お読みいただきありがとうございました!

参考サイト

https://gihyo.jp/dev/serial/01/tdd/0020
https://qiita.com/jnchito/items/eb3cfa9f7db752dcb796
https://twitter.com/t_wada/status/739646665183793152
https://speakerdeck.com/jnchito/number-vstat

スーパーソフトウエアの採用情報

あなたが活躍できるフィールドと充実した育成環境があります

blank
blank