C#でドメイン駆動開発Moqを使ったテスト駆動開発のやり方を解説!⑦

当サイトではアフィリエイト広告を利用しています。

C#でドメイン駆動開発

テスト駆動開発をしていく中で作成しているアプリケーションが外部と接触する箇所があります。データベースやファイルなどです。それらの箇所のテストコードを記述するのは困難なのですがそれを解決するツールにMoqというものがあります。このMoqを使えば比較的簡単に外部接触部分のテストコードを記述することができます。詳細に解説しているため是非ご覧ください。

Mock

ここでモックという概念が出てきます。モックとは自動テスト用にダミーのデータを流し込んでくるクラスです。ISensorRepositoryの具象クラスは当面このMockで代用します。書き方は2通りあります。ツールを使わずに自前でMockを作るやり方です。概念を理解するにはこのほうがわかりやすいのでまずこのやり方でやります。その後Moq(モッキュー)というクラスライブラリーを使用して記述するやり方を解説します。

TestsプロジェクトにSensorMockというクラスを新規作成します。

internal sealed class SensorMock : ISensorRepository
{
     public float GetData()
     {
           return 1.23456f;
     }
}

これで完成です。ISensorRepositoryの具象クラスが完成しました。internalとしたのはテストプロジェクト以外で使用できないようにするためです。sealedは継承不可ですね。C#はsealedがデフォルトで尽きませんが,.netFrameworkの著書はsealedがデフォルトつかないのは設計ミスだったなどという記載があります。要するにクラスは基本sealedで作成し,継承される段階でsealedを外すというほうが自然であるということです。GetData()では1.23456fを返却しています。なのでこのクラスをNewすればかならず1.23456fが返却されるということになります。

作成したSensorMockをMeasureViewModelにはめ込みましょう。といってもMeasureViewModelの中でSensorMockを生成してはいけませんし,Internalにしたのでできませんよね。そんなことをしたらViewModelの中はテストコードと本番コードでぐちゃぐちゃになります。解決方法としては,コンストラクタで注入します。

MeasureViewModelのコンストラクタを次のようにします。

public MeasureViewModel(ISensorRepository sensorRepoisitoy)
{
     _sensorRepository = sensorRepsository;
}

このコンストラクタがあれば外部からISensorRepositoryの具象クラスを指定できます。いまテストを実行するとコンパイルエラーになります。テストコードのMeasureViewModel生成時に引数を指定していません。そこで次のようにテストコードを書き換えます。

var viewModel = new MeasureViewModel(new SensorMock());

これでコンパイルエラーが消えます。テストを実行しましょう。テストもグリーンバーになりました。何が起きたかを見ていきましょう。

まずSensorMockのインスタンスををコンストラクタで注入しました。そのためMeasureViewModel内の_sensorRepositoryへのアクセスはすべてSensorMockのインスタンスが使用されます。テストコードでviewModel.Measure()としたとき,SensorMockのインスタンスが呼ばれ,1.23456fを返却します。ViewModelはその値を小数点以下2桁で丸めて,単位(m/s)をつけてMeasureValueにセットします。

よってテストコードの

viewModel.MeasureValue.Is(“1.23m/s”);

のテストは成功します。

以上がデータ取得の流れとなります。

現状,センサーに接続する技術的要因のロジックは一切記載せず,アプリケーションとしてコアな部分のテストに成功しました。

あとはInfrastructure層にセンサーと通信するロジックを書き,そのロジックがリポジトリーで定義した値を返せば,小数点以下桁数のまるめや,単位のつけ方は間違いなく行われると保証されています。

やり方としてはこれでよいのですが,自前で作るMockは少々手間がかかります。例えば今のテストコードに続けてもう一度Measure()を呼び出し,今度は違う値を返却する場合に今のままでは実現できません。

別のモックを作成するか,Mockで返却する値を変更できるような仕組みが必要です。それくらい出来なくはないですがMoqというライブラリーを使用すればもっと簡単に高度なことができます。なのでこれからはMoqを使ってテストコードを書いていきます。自前のMockは概念を理解するために作成したため,後程削除します。

Moq(クラスライブラリー)を使う

Moqを使いましょう。NugetでMoqを検索して入手してください。わからなければサンプルコードを参照してください

今のテストコードを次のように書き換えます。

var sensorMock = new Mock<ISensorRepository>();
var viewModel = new MeasureViewModel(sensorMock.Object);

これで先ほど作成したSensorMockとほとんど同じものが作成されました。sensorMock.Objectとした時点でISensorRepositoryのインスタンスが生成されます。

あとはMeasure()メソッド実行時に1.23456fを返すように定義しないといけません。

その方法も簡単です。今作ったsensorMock変数に対してSetupというものを呼び出します。

sensorMock.Setup(x=> x.GetData()).Return(1.23456f);

Setupの後のラムダ式でISensorRepositoryの関数を呼び出します。続くReturnの中で,その関数が呼ばれたときに返却される値を指定できます。

この場合,GetData()が呼ばれたら1.23456fを返すという定義ができました。実際これでテストをしてみましょう。グリーンバーのままですね。これであればもう一度違う値でMeasure()メソッドのテストを行うことができます。

sensorMock.Setup(x=> x.GetData()).Return(1.23456f);
viewModel.Measure();
viewModel.MeasureValue.Is(“1.23m/s”);

sensorMock.Setup(x=> x.GetData()).Return(2.2f);
viewModel.Measure();
viewModel.MeasureValue.Is(“2.2m/s”);

ポイントは「Measure」の中で実際にGetDataを呼び出しているため,Setupは少なくともテストでMeasure()を呼び出す前に記述必要があります。

これであれば,最大値,最小値のテストや,小数点以下の桁数が満たない場合は0で埋めるなどのテストも容易に行えます。