今回は「C#でテスト駆動開発をする方法」の
第2回
「C#で単体テストをする時の観点と
テスト駆動開発の5つの手順を徹底解説」
をお届けします。
さて前回は2つの値の足し算をするという
単純なAddメソッドを作成してテストコードを実装しました。
その時の最後に[TestMethod]を書き忘れないようにする対策や,
データベースを絡めたテストの方法などがあるといいましたが,
今回はそのあたりのテストの書き方を解説します。
前回までのおさらい
前回までのおさらいをしておきましょう。
まずテストされる側のコードとしてCalculationクラスに
Addメソッドが存在します。
【Calculation.cs】
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace TDD.Winform.Objects { public class Calculation { public int Add(int a,int b) { return a + b; } } }
テストコードではそのAddメソッドを呼び出して結果をテストしています。
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace TDDTest.Tests { [TestClass] public class UnitTest1 { [TestMethod] public void TestMethod1() { var calculation = new TDD.Winform.Objects.Calculation(); int result = calculation.Add(3, 5); Assert.AreEqual(7, result); } } }
前回のテストコードでは,
わざとテストが失敗するように3+5の結果を7と記載しましたので,
元に戻しておきましょう。
Assert.AreEqual(7, result);
の「7」の部分を「8」にしておいてください。
using System; using Microsoft.VisualStudio.TestTools.UnitTesting; namespace TDDTest.Tests { [TestClass] public class UnitTest1 { [TestMethod] public void TestMethod1() { var calculation = new TDD.Winform.Objects.Calculation(); int result = calculation.Add(3, 5); Assert.AreEqual(8, result); } } }
念のためテストを実行しておきましょう。
実行はテストエクスプローラーの「すべて実行」を
クリックするのでしたね。
緑のバーが出ればOKです。
俗にこれをグリーンバーと呼んでいます。(そのままですが)
テスト駆動開発の2つの間違い
[TestMethod]の書き忘れ対策にもなるのですが,テストコードには推奨されている書き方があります。
それがテスト駆動開発といわれるものです。
一言でいうと「テストコードを書いてから実装を書く」という事になります。
テスト駆動開発の勘違い1
「テストコードを先に書く」というとよく質問される内容で,
「1つの機能(画面など)の処理に対して,
すべてのテストコードを書いてから実装を書くのですか?」
といわれることがあります。 これは全く違います。
テストコードと実装のサイクルもっと短いサイクルで回します。
やり方はこの後ゆっくり解説するので安心してくださいね。
テスト駆動開発の勘違い2
2つ目に多い質問でこれもよく聞かれますが,
「単体テスト仕様書(ドキュメント)を作成していないので
テスト駆動開発できません」
というものです。
テスト駆動開発と単体テスト仕様書は全く関係ありません。
テストコードを書く際に単体テスト仕様書が出来上がっている必要はありません。
テスト駆動開発の5つの手順
それではテスト駆動開発の手順を解説します。
- 手順1 テストコードを書く
- 手順2 コンパイルエラーを取り除く
- 手順3 テストを失敗させる(レッドーバー)
- 手順4 実装をする(レッドバーを取り除く)
- 手順5 テストを成功させる(グリーンバー)
以上の5つのサイクルをぐるぐると回します。
何となくわかりますか?
実際にやって意味ないと分かりづらいと思うので実際にやってみましょう。
テスト駆動開発での書き方実演
それでは実際にやってみましょう。
手順1 テストコードを書く
なんのテストコードを書きましょうか?
実際にはプログラムの仕様を考えながらテストコードを書いていくことになります。
例えば2つの数値の掛け算をして値を返す
「Multiplication」という関数を作るとしましょう。
テストはどんな感じになるでしょうか?
例えばこんな感じになります。
[TestMethod] public void 掛け算() { var calculation = new Calculation(); Assert.AreEqual(10, calculation.Multiplication(2, 5)); } ※コードの一番上に「using TDD.Winform.Objects;」と記載してください。
まず初めに「public void 掛け算()」の部分ですが,
テストメソッドの名前を日本語にしています。
初めて見る人は違和感があるかもしれませんが,
この部分を日本語にしている人は結構います。
この部分にテストで何をしているかを書いておきます。
テストメソッドは外部から呼び出されることも,
引数を持つこともないため,
分かりやすいアルファベットの名前を持つ必要もなく,
メソッドの最初のXMLコメントを書く必要もありません。
なのでメソッド名を日本語にするのが
私はわかりやすくてベストだと思っています。
実際には「掛け算_OK」でOKパターンのテストにしたリ,
「掛け算_NG」でエラーパターンのテスト名にしたりと工夫できます。
続いて「var calculation = new Calculation();」
の部分は先ほどと同様です。
「TDD.Winform.Objects」の部分が長いのでusingに移しました。
次の「Assert.AreEqual(10, calculation.Multiplication(2, 5));」が
今回のメインの部分となります。
今回の2つの値の掛け算をする処理を作ることでしたから,
「Multiplication」という名前のメソッドを
Calculationクラスに追加することとします。
2つの値の掛け算ですから,intの引数を2つ受け取ることになるので,
「2」と「5」を引数で投げています。
そうすると正しく動作していれば「10」が返却されることになるはずなので
AreEqualの左側は「10」としています。
これでテストを実行しましょう。
「え?」
「コンパイルエラーが出てるって?」
そうですよね。でもOKです。
これこそがテスト駆動開発です。
手順2 コンパイルエラーを取り除く
手順の2ではコンパイルエラーを取り除くことです。
どうやって取り除くかというと,
CalculationクラスにMultiplicationメソッドを作ればいいのですが,
Visual Studioではテスト駆動開発をサポートする機能がついているので,
使ってみましょう。
まずコンパイルエラーが出ている箇所(Multiplicationの部分)にカーソルを当てます。
その状態で「Control」キーを押しながら「.」ピリオドキーを押してみてください。
「メソッドCalculation.Multiplicationを生成します」と表示されます。
その状態でEnterキーを押しましょう。
すると
コンパイルエラーが消えましたね。
消えたという事は...
「Multiplication」の部分で「F12」を押下してCalculationクラスを見てみましょう。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace TDD.Winform.Objects { public class Calculation { public int Add(int a,int b) { return a + b; } public int Multiplication(int v1, int v2) { throw new NotImplementedException(); } } }
「あら不思議!」というか「あら親切に!」
Multiplicationメソッドができています。
ちゃんとintの引数が2つと戻り値がintになっています。
テスト駆動開発での良い点としてはこの実装部分の自動生成機能があります。
いままで(テスト駆動をする前)は実装部分で
Multiplicationメソッドという名前を付けて実装していましたが,
テスト駆動ではテストコードでMultiplicationという名前を付けて,
コンパイルエラーを取り除くためにコードを自動生成し,
実装部分を作成します。
最初はテストコードの記載中にインテリセンスが表示されないため,
違和感があるかもしれませんが,
テストコードを書きながら命名していっていると思うと
非常に便利な機能であることが実感できると思います。
それではまたテストコードに戻りましょう。
[TestMethod] public void 掛け算() { var calculation = new Calculation(); Assert.AreEqual(10, calculation.Multiplication(2, 5)); }
いまはコンパイルエラーがなくなった状態です。
それでは晴れてテストを実行しましょう。
「え?!」
「テストが失敗しているって!」
OKです。それがテスト駆動開発です。
それこそが手順3「テストコードを失敗させる」です。
手順3「テストコードを失敗させる」
なぜ失敗させるかって? テスト駆動開発の考え方は,
「テストコードをパスさせるために実装を書く」という発想です。
だから無駄な実装コードは生まれません。
ようするに,テストが失敗するから,
それをパスさせるためだけに実装コードを書くというのが
一番シンプルで無駄のない実装であるという考え方なのです。
それにもし,テストメソッドに不備があればこの段階で気づくことができます。
例えば最初から言っていた[TestMethod]の書き忘れがもしあった場合は
この段階で気が付きます。
今はMultiplicationメソッドを自動生成しただけで,
その中身は未実装を示す「 NotImplementedException」の
例外が生成されています。
普通に呼び出せば間違いなくテストは失敗するはずです。
それなのにテスト一覧がすべてOKでグリーンバーになったら,
「おや?」 「今作ったテストが動作していないぞ!?」
って気が付くことができます。
この手順がなかったら,
テストが成功していると勘違いして,
実装を繰り返し,バグがあっても検出できないことになります。
だから「まずレッドバーを出す」
そしてレッドバーをグリーンバーに変えるためだけに実装をしていきます。
手順4 実装をする(レッドバーを取り除く)
それでは手順の4です。ここでようやく実装をします。
いまはレッドバーが出ているので,
Multiplicationメソッドを正しく書かないとグリーンバーは手に入りません。
public int Multiplication(int v1, int v2) { throw new NotImplementedException(); }
とりあえず未実装を示す例外
「throw new NotImplementedException();」を削除します。
これがあってはいつまでもグリーンバーは手に入りません。
ちなみにこの例外は実装するまでは消さないほうがいいです。
「後で実装しよう」などとしている場合はこの例外のおかげでエラーになり,
実装漏れをする心配なありません。
さて,それでは実装をしましょう。
public int Multiplication(int a, int b) { return a * b; }
まず引数の「v1」「v2」はaとbに変更しました。
実際はプロジェクトの命名規約に従って命名しましょう。
自動生成の変数名がそのまま使えることはまずないです。
実装部分の「return a * b;」はaとbの掛け算をして返却しています。
この状態でテストを実行しましょう。 テストが成功しました。
これで 「手順5 テストを成功させる(グリーンバー)」も完了したことになります。
おさらい
もう一度テスト駆動の手順を見てみましょう。
- テストコードを書く
- コンパイルエラーを取り除く
- テストを失敗させる(レッドーバー)
- 実装をする(レッドバーを取り除く)
- テストを成功させる(グリーンバー)
今見れば,内容はすんなり入ってくるのではないでしょうか?
まずテストコードをコンパイルエラーを出しながら,
メソッド名や引数を考えてインテリセンスを使わずに書いていきます。
その後コンパイルエラーを取り除くために
Visual Studioのサポート機能でメソッドを自動生成します。
メソッド以外にもプロパティなども自動生成できます。
メソッド名やプロパティ名はテストをしながら命名していきます。
そしてレッドバーを確認し,グリーンバーにする。
といった手順を,細かい単位で機能を満たすまでやり続けます。
だから最初の勘違いポイントとして,
全部テストコードを書いてから実装するっていうのは全然そんなことなく,
テストを書いてすぐに実装してっていう短いサイクルの繰り返しだし,
単体テスト仕様書がないからテスト駆動できないっていうのも
全然違う話になります。
実装したい機能がわかっていれば,
それを実現したらこんな結果が返ってくるはずって思いながら,
テストケースを考えて,
それをパスするために実装するってことになります。
このやり方を続けると,すべてのコードがテストコードでカバーされたすばらしいプログラムになります。しかしながら,今回は独立したメソッドに対してのテストコードを書いたので,比較的簡単でしたが。実際にはもっと複雑で,テストコードを書けない場所とか,書きづらい場所とかいろいろあったりして,テストコードを書く技術が必要になります。詳しい実装方法はUdemyのC#でテスト駆動開発をする方法を参考にしてください。