はぅ君さんが、NUnit(と、それを動かすためのJenkins)に手を出されたらしい。
本日はJenkins氏を雇ってみた。今回の対象はC#とC++。NUnitもやってくれるすぐれた執事さん。仲良くやっていけそう。
AndroidとかObjective-Cのビルドもできるのかな。Androidはndk-build辺りを使えばできそうだけど、iOSはさすがに駄目かな。
— はぅ君 (@Hau_kun) 2014, 12月 11
@tercel_s 新米ひよっこです(・◇・)
— はぅ君 (@Hau_kun) 2014, 12月 11
NUnitでテストを書く際、セットで使うとちょっと幸せになれる(かもしれない)NSubstituteというライブラリがあります。
@tercel_s ほよー、そんなものもあるんですね(・-・)φメモメモ
— はぅ君 (@Hau_kun) 2014, 12月 11
というわけで本日は、はぅ君さんがご存知なさそうだったNSubstituteの便利さについてご紹介します。
『レガシーコード改善ガイド (Object Oriented SELECTION)』を題材に、リファクタリング → NUnitを使用したテスト → NUnit + NSubstituteを使用したテストをステップバイステップで書いていきます。
なお、本記事の執筆にあたり使用した環境は、Visual C# 2013 + NUnit 2.6.4 + NSubstitute 1.8.0 です。
ナイスな例題
ある販売管理システムにSaleというクラスがあるとします。このクラスのScanというメソッドは、顧客が購入しようとしている品物のバーコードを受け取ります。Scanメソッドが呼び出されると、Scanオブジェクトは読み取った品物の名前と価格とをレジの画面に表示します。
このクラスをテストして、正しい文字列が表示されるかどうかを確認するにはどうすればよいでしょうか。
ちなみにこのScan()
メソッド、データベースへのアクセスやレジ画面のAPI呼び出しが深く埋め込まれているモンスターメソッドであると仮定しましょう。
public class Sale { /// <summary> /// バーコードを受け取り、 /// 品物の名前と価格をレジの画面に表示します。 /// </summary> /// <param name="barcode">バーコード</param> public void Scan(string barcode) { // : // : // とてつもなく長いメソッドにつき省略 // : // : } }
なにはともあれリファクタリング
もし、画面の更新が行われている箇所を特定して別クラス(クラス名は引用元に従いArtR56Display
)に切り出すことができたら(下図参照)、テストしやすくなるかもしれません。
このときArtR56Display
クラスの役割は、渡された引数line
をそのままレジの画面に表示するだけです。外から見たシステム全体の動きは全く変わっていません。
さらに、このArtR56Display
クラスを、テストのときだけ擬装オブジェクトにすり替えできるよう、Interfaceを一皮かぶせてあげます。
public interface IDisplay { void ShowLine(string line); }
Sale
の中で、IDisplay
は以下のように使われるでしょう。
public class Sale { private IDisplay display; public Sale(IDisplay display) { this.display = display; } public void Scan(string barcode) { // : // 前略 // : // 画面表示部 string itemLine = item.Name + " " + item.Price; display.ShowLine(itemLine); // : // 後略 // : } }
画面表示部を丸ごとIDisplay
に委譲してあげるイメージですね。
NUnitでテストを書こう!
ここからテスト用のプロジェクトに書くコードです。
まず、ArtR56Display
の擬装クラスFakeDisplay
の実装です。
public class FakeDisplay : IDisplay { private string _lastLine = string.Empty; public string LastLine { get{ return _lastLine; } private set{ _lastLine = value; } } public void ShowLine(string line) { _lastLine = line; } }
こいつには、直前にShowLine()
で出力した文字列を、LastLine
プロパティに保持する隠し機能を持たせます。これで、LastLine
にアクセスすると、出力文字列を取得することができ、期待値との突合ができるようになる寸法です。
NUnitを用いたテストコードを以下に示します。
[TestFixture] public class SaleTest { [Test] public void TestDisplayAnItem() { FakeDisplay display = new FakeDisplay(); Sale sale = new Sale(display); sale.Scan("1"); Assert.AreEqual(@"ブルーレット置くだけ ¥1,000-", display.LastLine); } }
……ちなみにMicrosoftが密かに推奨しているMSTestを利用した場合のテストコードは以下のようになります。ほとんど一緒ですな。
[TestClass] public class SaleTest { [TestMethod] public void TestDisplayAnItem() { FakeDisplay display = new FakeDisplay(); Sale sale = new Sale(display); sale.Scan("1"); Assert.AreEqual(@"ブルーレット置くだけ ¥1,000-", display.LastLine); } }
テスト自体は一応これでもよいのですが、擬装クラスを書いたせいでテストコードが増えてしまってたいへんです。
もう少しスマートにならないものでしょうか。
NSubstituteを使ってテストコードを減らす
ここからちょっとした小技で、NSubstituteというライブラリを使います。NuGetからインストール可能なので導入もベリーイージーです。
FakeDisplay
を作らなくてもよくなるのでテストコードがすっきりします。
以下に、NSubstitute
の使用例を示します。
Sale.Scan()
経由でIDisplay#ShowLine()
が呼び出されたときに、IDisplay#ShowLine()
の受け取ったパラメータが期待値と一致するかどうかを確認するテストコードです。
[TestFixture] public class SaleTest { [Test] public void TestDisplayAnItem() { var substitute = Substitute.For<IDisplay>(); Sale sale = new Sale(substitute); sale.Scan("1"); // IDisplay#ShowLineに渡された引数が期待値と一致するかを確認 substitute.Received().ShowLine(@"ブルーレット置くだけ ¥1,000-"); } }
これで、わざわざ《テストのためだけの余分なクラス》を作る手間が一つ減るので、ちょっと幸せになる気がします。
なお、期待値を「ルーレット置くだけ ¥1,000-」に変更してわざとテストを失敗させた結果がこちら。
めでたし。
いやめでたくない。
NSubstituteのそのほかの使い方は公式のドキュメントに書いてあるんじゃないでしょうかね(投げやり