本エントリーはC#向けです。言語によっては同じような実装ができない(必要ない)場合もありますのでご注意ください。
課題
enumによる処理分岐をswitch文(switch式でも実質同じ)を記載したとき、enumが取り合えない値であった場合はdefaultで例外をスローするのは一般的な実装です。
ICellLinecellLine;switch(_column.VerticalAlignment){caseVerticalAlignment.Top:cellLine=GetTopCellLine();break;caseVerticalAlignment.Center:cellLine=GetCenterCellLine();break;caseVerticalAlignment.Bottom:cellLine=GetBottomCellLine();break;default:thrownewArgumentOutOfRangeException();}しかしdefaultの行は、普通にテストすると通過しないためカバレッジ率が低下します。
目的
さて、この課題を解決する目的はどこにあるのでしょうか?
私は別にカバレッジ100%原理主義者ではありませんから、カバレッジを通すこと自体が目的ではありません。目的はふたつあります。
- テストの維持を容易にするため
- enumに新たな値が追加されたときの不具合を減らすため
テストの維持を容易にするため
テストでカバレッジが通過していない箇所は「テストをしなくても良い理由」が必要です。そしてそれは、プロダクトが継続している限り管理し続ける必要があります(私は通常、その理由をコードコメントに残します)。
テストを実施する都度、通過していない箇所はそれで良いのか判断しつづける(つまりコメントを目で確認する)のは非常につらいことですから、通せるところは通してしまった方が結果的に楽なケースが多いでしょう。
enumに新たな値が追加されたときの不具合を減らすため
例えば先のコードは、つぎのように記述することで同じように動作しますし、カバレッジもenumの範囲内で100%になります。
ICellLinecellLine;switch(_column.VerticalAlignment){caseVerticalAlignment.Top:cellLine=GetTopCellLine();break;caseVerticalAlignment.Center:cellLine=GetCenterCellLine();break;default:cellLine=GetBottomCellLine();break;}「case VerticalAlignment.Bottom」を削除してdefaultにその実装を移動しています。
しかしこの実装をしてしまうと、VerticalAlignmentに4番目の値が追加されたときにBottomと同じ挙動で動作してしまいます。
新しいenum値が追加されてもコードが修正されていない場合、例外をスローし、早期にバグを発見できるようにするべきです。このため、この解放は利用できません。
今回はこの問題を解決するテスト手法についてまとめたいと思います。
結論
- 取りえない値からキャストしてenumを生成しdefaultを通過させ、例外がスローすることを確認する
つぎのようにします。
varhorizontalAlignment=(HorizontalAlignment)4;// HorizontalAlignmentの正常値は1~3C#のenumは取りえない値からも正常にキャストできます。だからこそswitch文にdefaultが必要なのですが、今回はそこを逆用して異常な値を取るenum値を生成してテストします。これで前述のdefaultは通過し、カバレッジも取得できるようになります。
こうして生成した異常なenumを渡して例外がスローされるテストコードを記述しておきます。これで、ふたつの目的はクリアされます。
カバレッジは通過しますし、enumに新たな値が追加されたとき、分岐を追加せずに実行すると例外が発生しバグを早期に補足できます。
まとめ
- enum & switch文のコードはdefaultも記述しよう
- defaultもちゃんとコードによるテストを通してカバレッジを取ろう
- defaultのテストはenumの範囲外の値からキャストして行い、例外がスローされることを確認しよう
Special Thanks
このアイディアは、つぎのTwitterでつぶやいたところ発生した議論をまとめたものです。
switch分の通らないdefalutでカバレッジ下がるの、なんかいい対処方法ないのかな?
— NAKAMURA Atsushi (@nuits_jp) July 5, 2020
@haxeさん、@_midoliy_さん、@okazukiさん(登場順)アドバイスありがとうございました。