これは私が非常に興味を持っているトピックです。EFやNHibernateなどのテクノロジをテストすべきではないと言う純粋主義者はたくさんいます。彼らは正しいです、彼らはすでに非常に厳格にテストされており、以前の回答が述べたように、所有していないものをテストするのに膨大な時間を費やすことはしばしば無意味です。
ただし、その下にデータベースを所有しています。これは、私の意見ではこのアプローチが失敗するところです。EF/ NHが正しく機能していることをテストする必要はありません。マッピング/実装がデータベースで機能していることをテストする必要があります。私の意見では、これはテストできるシステムの最も重要な部分の1つです。
ただし厳密に言えば、単体テストの領域から統合テストに移行していますが、原則は同じです。
最初に行う必要があるのは、DALをモック化して、BLLをEFおよびSQLとは独立してテストできるようにすることです。これらはあなたの単体テストです。次に、DALを証明するために統合テストを設計する必要があります。私の意見では、これらはすべて同じくらい重要です。
考慮すべき点がいくつかあります。
- データベースは、テストごとに既知の状態である必要があります。ほとんどのシステムでは、このためにバックアップまたは作成スクリプトを使用します。
- 各テストは繰り返し可能でなければならない
- 各テストはアトミックである必要があります
データベースを設定するには、主に2つの方法があります。1つ目は、UnitTestのDB作成スクリプトを実行する方法です。これにより、ユニットテストデータベースは各テストの開始時に常に同じ状態になります(これをリセットするか、トランザクションで各テストを実行してこれを確認できます)。
あなたの他のオプションは私が何をするか、個々のテストごとに特定のセットアップを実行することです。これが2つの主な理由で最良のアプローチだと思います。
- データベースはよりシンプルで、テストごとにスキーマ全体を必要としません
- 各テストはより安全です。作成スクリプトで1つの値を変更しても、他の何十ものテストが無効になることはありません。
残念ながら、ここでの妥協はスピードです。これらのすべてのテストを実行し、これらのすべてのセットアップ/破棄スクリプトを実行するには時間がかかります。
最後のポイントとして、ORMをテストするためにこのような大量のSQLを作成することは非常に困難な作業になる可能性があります。これは私が非常に厄介なアプローチをするところです(ここの純粋主義者は私に同意しません)。ORMを使用してテストを作成します!システムのDALテストごとに個別のスクリプトを作成するのではなく、オブジェクトを作成し、コンテキストにアタッチして保存するテストセットアップフェーズがあります。次に、テストを実行します。
これは理想的なソリューションとはほど遠いですが、実際には管理が非常に簡単であることがわかります(特に数千のテストがある場合)。そうしないと、大量のスクリプトを作成することになります。純度よりも実用性。
私のアプローチが変わったとき、私は間違いなく数年(月/日)でこの回答を振り返り、自分自身に同意しません。ただし、これは私の現在のアプローチです。
上記で述べたすべてを試してまとめると、これは私の典型的なDB統合テストです。
[Test]
public void LoadUser()
{
this.RunTest(session => // the NH/EF session to attach the objects to
{
var user = new UserAccount("Mr", "Joe", "Bloggs");
session.Save(user);
return user.UserID;
}, id => // the ID of the entity we need to load
{
var user = LoadMyUser(id); // load the entity
Assert.AreEqual("Mr", user.Title); // test your properties
Assert.AreEqual("Joe", user.Firstname);
Assert.AreEqual("Bloggs", user.Lastname);
}
}
ここで注目すべき重要な点は、2つのループのセッションが完全に独立していることです。RunTestの実装では、コンテキストがコミットおよび破棄され、データが2番目の部分のデータベースからのみ取得できることを確認する必要があります。
2014年10月13日編集
私はおそらくこのモデルを今後数ヶ月にわたって修正するだろうと言っていました。私は上記で提唱したアプローチを大部分は支持していますが、テストメカニズムを少し更新しました。TestSetupとTestTearDownでエンティティを作成する傾向があります。
[SetUp]
public void Setup()
{
this.SetupTest(session => // the NH/EF session to attach the objects to
{
var user = new UserAccount("Mr", "Joe", "Bloggs");
session.Save(user);
this.UserID = user.UserID;
});
}
[TearDown]
public void TearDown()
{
this.TearDownDatabase();
}
次に、各プロパティを個別にテストします
[Test]
public void TestTitle()
{
var user = LoadMyUser(this.UserID); // load the entity
Assert.AreEqual("Mr", user.Title);
}
[Test]
public void TestFirstname()
{
var user = LoadMyUser(this.UserID);
Assert.AreEqual("Joe", user.Firstname);
}
[Test]
public void TestLastname()
{
var user = LoadMyUser(this.UserID);
Assert.AreEqual("Bloggs", user.Lastname);
}
このアプローチにはいくつかの理由があります。
- 追加のデータベース呼び出しはありません(1つのセットアップ、1つのティアダウン)
- テストははるかに詳細であり、各テストは1つのプロパティを検証します
- Setup / TearDownロジックがTestメソッド自体から削除されました
これにより、テストクラスが単純になり、テストがより詳細になります(単一のアサートで十分です)。
2015年5月3日を編集
このアプローチの別の改訂。クラスレベルのセットアップは、プロパティの読み込みなどのテストに非常に役立ちますが、さまざまなセットアップが必要な場合にはあまり役に立ちません。この場合、ケースごとに新しいクラスを設定するのはやりすぎです。
これを支援するために、私は2つの基本クラスSetupPerTest
とを使用する傾向がありSingleSetup
ます。これらの2つのクラスは、必要に応じてフレームワークを公開します。
にSingleSetup
は、最初の編集で説明したのと非常によく似たメカニズムがあります。例は
public TestProperties : SingleSetup
{
public int UserID {get;set;}
public override DoSetup(ISession session)
{
var user = new User("Joe", "Bloggs");
session.Save(user);
this.UserID = user.UserID;
}
[Test]
public void TestLastname()
{
var user = LoadMyUser(this.UserID); // load the entity
Assert.AreEqual("Bloggs", user.Lastname);
}
[Test]
public void TestFirstname()
{
var user = LoadMyUser(this.UserID);
Assert.AreEqual("Joe", user.Firstname);
}
}
ただし、正しいエンティティのみが読み込まれることを保証する参照では、SetupPerTestアプローチを使用できます。
public TestProperties : SetupPerTest
{
[Test]
public void EnsureCorrectReferenceIsLoaded()
{
int friendID = 0;
this.RunTest(session =>
{
var user = CreateUserWithFriend();
session.Save(user);
friendID = user.Friends.Single().FriendID;
} () =>
{
var user = GetUser();
Assert.AreEqual(friendID, user.Friends.Single().FriendID);
});
}
[Test]
public void EnsureOnlyCorrectFriendsAreLoaded()
{
int userID = 0;
this.RunTest(session =>
{
var user = CreateUserWithFriends(2);
var user2 = CreateUserWithFriends(5);
session.Save(user);
session.Save(user2);
userID = user.UserID;
} () =>
{
var user = GetUser(userID);
Assert.AreEqual(2, user.Friends.Count());
});
}
}
要約すると、どちらのアプローチも、テストする対象に応じて機能します。