複数のレイヤーを持つアプリケーションに取り組んでいます。データソースからデータを取得して保存するデータアクセスレイヤー、データを操作するビジネスロジック、画面にデータを表示するユーザーインターフェイス.
また、ビジネスロジックレイヤーの単体テストも行っています。唯一の要件は、ビジネスレイヤーロジックのフローをテストすることです。そこで、Moqフレームワークを使用してデータアクセスレイヤーをモックし、ビジネスロジックレイヤーをMS Unitでユニットテストします。
ユニットテストを実行できるように、インターフェイスプログラミングを使用して設計を可能な限り分離します。ビジネスレイヤーは、インターフェイスを介してデータアクセスレイヤーを呼び出します。
ビジネスロジックメソッドの1つをテストしようとすると、問題が発生します。このメソッドはいくつかの作業を行い、オブジェクトを作成してデータアクセスレイヤーに渡します。そのデータアクセスレイヤーメソッドをモックしようとすると、正常にモックできません。
ここで私は私の問題を示すためのデモコードを作成しようとしています。
モデル:
public class Employee
{
public string Name { get; set; }
}
データアクセス層:
public interface IDal
{
string GetMessage(Employee emp);
}
public class Dal : IDal
{
public string GetMessage(Employee emp)
{
// Doing some data source access work...
return string.Format("Hello {0}", emp.Name);
}
}
ビジネスロジックレイヤー:
public interface IBll
{
string GetMessage();
}
public class Bll : IBll
{
private readonly IDal _dal;
public Bll(IDal dal)
{
_dal = dal;
}
public string GetMessage()
{
// Object creating inside business logic method.
Employee emp = new Employee();
string msg = _dal.GetMessage(emp);
return msg;
}
}
単体テスト:
[TestMethod]
public void Is_GetMessage_Return_Proper_Result()
{
// Arrange.
Employee emp = new Employee; // New object.
Mock<IDal> mockDal = new Mock<IDal>();
mockDal.Setup(d => d.GetMessage(emp)).Returns("Hello " + emp.Name);
IBll bll = new Bll(mockDal.Object);
// Act.
// This will create another employee object inside the
// business logic method, which is different from the
// object which I have sent at the time of mocking.
string msg = bll.GetMessage();
// Assert.
Assert.AreEqual("Hello arnab", msg);
}
モック時の単体テストケースでは、Employeeオブジェクトを送信していますが、ビジネスロジックメソッドを呼び出すと、メソッド内に別のEmployeeオブジェクトが作成されます。そのため、オブジェクトをモックできません。
その場合、問題を解決できるように設計するにはどうすればよいですか?
Employee
を使用してnew
オブジェクトを直接作成する代わりに、クラスBll
は、コンストラクターを通じて注入されるメソッドEmployeeFactory
でcreateInstance
クラスを使用できます。
class EmployeeFactory : IEmployeeFactory
{
public Employee createInstance(){return new Employee();}
}
コンストラクタは、IEmployeeFactory
インターフェースを介してファクトリオブジェクトを取得する必要があるため、「実際の」ファクトリをモックファクトリで簡単に置き換えることができます。
public class Bll : IBll
{
private readonly IDal _dal;
private readonly IEmployeeFactory _employeeFactory;
public Bll(IDal dal, IEmployeeFactory employeeFactory)
{
_dal = dal;
_employeeFactory=employeeFactory;
}
public string GetMessage()
{
// Object creating inside business logic method
// *** using a factory ***
Employee emp = _employeeFactory.createObject();
// ...
}
//...
}
モックファクトリは、テストに必要なあらゆる種類のEmployee
オブジェクトをテストに提供できます(たとえば、createInstance
は常に同じオブジェクトを返すことができます)。
class MockEmployeeFactory : IEmployeeFactory
{
private Employee _emp;
public MockEmployeeFactory()
{
_emp = new Employee();
// add any kind of special initializing here for testing purposes
}
public Employee createInstance()
{
// just for testing, return always the same object
return _emp;
}
}
テストでこのモックを使用すると、うまくいくはずです。
私はそれをテストする単一のユニットとして扱います。
Employee
オブジェクトの作成元であるすべての入力を制御している限り、テストされたオブジェクトで作成されたという事実は問題になりません。引数の内容が期待と一致した場合に期待される結果を返すには、モックメソッドが必要です。
明らかに、これはモックメソッドにカスタムロジックを提供する必要があることを意味します。 「for x return y」のようなモックだけでは、高度なロジックをテストできないことがよくあります。
実際、テストではnotを使用すると、本番環境とは異なるオブジェクトが返されるようにする必要があります。そうした場合、作成するコードをテストしないためです。ただし、そのコードは製品コードの不可欠な部分であるため、テストケースでもカバーする必要があります。
これは一部のテストツールの欠点であり、常にインターフェイスを使用する必要があり、すべてを、インターフェイスベースのオブジェクトを別のオブジェクトと交換できるように作成する必要があります。
ただし、より優れたツールがあります。静的オブジェクトやグローバルオブジェクトを含め、任意のオブジェクトを交換できるMicrosoft Fakes(以前はMolesと呼ばれていました)を使用してください。これは、オブジェクトを置き換えるためのより低レベルのアプローチを採用しているため、慣れ親しんだテストの記述方法を維持しながら、どこでもインターフェースを使用する必要はありません。