2つ目のテストケース
NUnitではテストコードをリファクタリングしてパラメタライズドテストに持ち込むのが常道ですが、MSTestにはパラメタライズドテストがありません。MSTestでは、テストケースごとにテストメソッドを書いていくのが基本になります。
さて、2つ目のテストケースはどうしましょう? まず、割り算をするところだけを実装しましょうか。そのためには、四捨五入しなくても結果が正しくなるようなテストケースを選びます。
[TestMethod()] public void Calc打率Test02_打席数2打数2安打数1のとき_打率0点5() { int 打席数 = 2; int 打数 = 2; int 安打数 = 1; decimal 期待値 = 0.5m; decimal 打率 = (new 野球選手()).Calc打率(打席数, 打数, 安打数); Assert.AreEqual<decimal>(期待値, 打率); } //※行数を減らすために、先ほどとは書き方を変えています。
このテストを実行し、予想通りのREDになることを確認します。テストメソッドとテストメソッドの間を右クリックして[テストの実行]を選ぶと、そのソースコードファイル内の全テストが実行されます。あるいは、下図のようにテストビュー(右側のペイン)で複数選択して実行することもできます。
テストビューを出すには、メニュー[テスト]-[ウィンドウ]-[テスト ビュー]と選びます。テストビューでは、複数のテストを選択して実行することができます。
また、テストビューの上部に「グループ化」というドロップダウンリストがあります。ここで[ソリューション]を選べば、ソリューションに含まれている全テストを、ソリューション名を選択するだけで実行できます。上図のように[クラス名]を選ぶと、テストクラスごとにグルーピングされるので、クラス名を1つ選ぶだけでそこに含まれるテストをすべて実行できます。「今テストすべき範囲はどれだけか?」を考えて、適宜グルーピングを切り替えて使います(「テストケースを追加したのでREDになるはず」という時は、そのテストケースだけ。「これでオールGREENのはず」というときは、テストクラス全体~ソリューション全体)。
※ キーボードショートカットでも範囲を変えてテストを実行できます。メニューの[テスト]-[実行]を見てください。
REDを確認できたので、2つ目のテストケースを通るように製品コードに手を加えられます。
public decimal Calc打率(int 打席数, int 打数, int 安打数) { return (decimal)安打数 / 打数; // ←変更 }
テストをすべて実行し、オールGREENになることを確認します。
3つ目のテストケース
今度は、割り算の結果を四捨五入する部分を実装しましょう。四捨五入するロジック自体は.NET Frameworkのクラスライブラリを使いますから、それは信用できるとして、四捨五入されるケースを1つ書くだけでよいでしょう。
[TestMethod()] public void Calc打率Test03_打席数10000打数10000安打数5のとき_打率0005が四捨五入されて001() { int 打席数 = 10000; int 打数 = 10000; int 安打数 = 5; decimal 期待値 = 0.001m; decimal 打率 = (new 野球選手()).Calc打率(打席数, 打数, 安打数); Assert.AreEqual<decimal>(期待値, 打率); }
追加したテストを実行し、予想通りのREDになることを確認します。エラーメッセージは次のようになるはずです。
Assert.AreEqual failed. Expected:<0.001>. Actual:<0.0005>.
予想通りのREDにならなかった場合(例えばActualが<0.005>など)、テストケースの記述をどこか間違えていることになります。REDが確認できたら、製品コードに手を入れます。Math.Round(と打ったところでインテリセンスが表示してくれる引数リストの候補をいくつか見ていると、四捨五入の方式が決められていなかったことに気づくでしょう。とりあえず5は常に切り上げることにして、後で確認するためTODO:コメントに書いておきましょう。
public decimal Calc打率(int 打席数, int 打数, int 安打数) { return Math.Round((decimal)安打数 / 打数, 3, MidpointRounding.AwayFromZero); //←変更 //TODO: 四捨五入方式は AwayFromZero で良いか? }
すべてのテストを実行し、オールGREENを確認します。
今回は、テストケースを3つ書いて、1つの同値クラス(打数=0、安打数=任意、返値:打率)を実装しました。「三角測量」(テストケース2つ)よりも、さらに多くのテストを書きました。
実装に自信がないときは、このように細かくステップを踏んで進めていきます。逆に、自信があるときは、三角測量もせずに最初から「明白な実装」をして、テストケースを減らします(ただし、テストケースを通すため以上の製品コードは書かないこと)。
四捨五入で変化しないケース(4桁目以降がずっと0)・切り捨てられるケース・切り上げられるケースに、同値クラスを分割すべきだという考え方もできます。ここでは、四捨五入はライブラリー(Mathクラス)に任せるので、「Mathクラスの計算結果を表示するだけ(その内容は問わない)」と考えています。
メソッドの外部設計を考えるとき、このように「他のメソッドの結果をそのまま使うだけなので、その同値分割はここでは考えない」としていかないと、「刺激と反応」の表が膨らんでしまい、収拾が付かなくなります(テストケースの爆発)。逆に言えば、メソッドの外部設計が複雑すぎる(=テストケースのバリエーションが多すぎる)と感じたときは、メソッドを分割することを検討します。
4つ目のテストケース(例外のテスト)
以上で打率を計算する部分は実装できました。最後に、「打数が0のときには例外が発生する」が残っています。
NUnitではAssert.Throws()を使って例外を確認するテストが書けました(前回の記事(p.4)の「不安をテストに」)。MSTestにそれはありませんので、次のようにします。2通りの書き方ができます。
[TestMethod()] public void Calc打率Test04a_打席数2打数0安打数0のとき_ゼロ除算例外() { int 打席数 = 2; int 打数 = 0; int 安打数 = 0; 野球選手 p = new 野球選手(); try { p.Calc打率(打席数, 打数, 安打数); Assert.Fail("例外が出なかった"); } catch (DivideByZeroException) { // テスト OK ! } }
[TestMethod()] [ExpectedException(typeof(DivideByZeroException))] public void Calc打率Test04b_打席数2打数0安打数0のとき_ゼロ除算例外() { int 打席数 = 2; int 打数 = 0; int 安打数 = 0; 野球選手 p = new 野球選手(); p.Calc打率(打席数, 打数, 安打数); }
最初の書き方は、テストコードは長くなりますが、例外が発生するはずの箇所を限定できます。予定外の場所(例えばインスタンスを作る行)で例外が出てしまった場合でも、正しくREDにできます。
下のExpectedException属性を使った書き方は、簡潔にテストを書けますが、予定外の場所で例外が出てもテストにパスしてしまいます。
さて、このテストは、実行してみるとGREENになります。製品コードに手を入れる必要はありませんので、これで「お題その1」のテストファーストは完了です。
ここまでで、打率計算をMSTestでTDDするために必要な知識は解説できたので、「お題その2」以降については説明しませんが、前回の記事を参考にして、最後まで挑戦してみてください。

