[解決済み] Entity Framework 6のユニットテストはどうなっているのか、気になりますか?
質問
私はユニットテストとTDD全般を始めたばかりです。以前にも手を出したことがありますが、今は自分のワークフローに追加して、より良いソフトウェアを書こうと決心しています。
昨日、これを含むような質問をしたのですが、それだけで質問になってしまうようです。 私は、コントローラからビジネスロジックを抽象化し、EF6を使用して特定のモデルとデータインタラクションにマッピングするために使用するサービスクラスの実装を開始するために腰を下ろしました。
問題は、EFをリポジトリで抽象化したくない(特定のクエリなどではサービス外でまだ利用可能)、自分のサービスをテストしたい(EF Contextが使用される)ため、すでに自分自身でブロックしていることです。
ここで疑問なのですが、このようなことをする意味はあるのでしょうか?もしそうなら、IQueryableによって引き起こされたリークする抽象化、および以下のような多くの素晴らしい投稿を踏まえて、人々はどのようにそれを行っているのでしょうか? ラディスラフ・ムルンカ 特定のデータベースではなくインメモリ実装で作業する場合、Linqプロバイダの違いのためにユニットテストは簡単にはいかないというテーマについてです。
私がテストしたいコードは、かなり単純なようです。(これは私がやっていることを理解するためのダミーコードで、TDDを使用して作成を推進したいのです)
コンテキスト
public interface IContext
{
IDbSet<Product> Products { get; set; }
IDbSet<Category> Categories { get; set; }
int SaveChanges();
}
public class DataContext : DbContext, IContext
{
public IDbSet<Product> Products { get; set; }
public IDbSet<Category> Categories { get; set; }
public DataContext(string connectionString)
: base(connectionString)
{
}
}
サービス
public class ProductService : IProductService
{
private IContext _context;
public ProductService(IContext dbContext)
{
_context = dbContext;
}
public IEnumerable<Product> GetAll()
{
var query = from p in _context.Products
select p;
return query;
}
}
現在、私はいくつかのことをやろうとしている心境です。
- EF Contextをモック化し、以下のようなアプローチをとる。 ユニットテスト時のEFのモック化 あるいは、moq のようなインターフェース上で直接モッキングフレームワークを使用する - ユニットテストはパスするかもしれないが、必ずしもエンドツーエンドで動作するとは限らないという痛みを引き受け、それらを統合テストでバックアップする?
- のようなものを使うといいかもしれません。 努力 を使用してEFをモックする - 私は使ったことがないので、他の人が実際に使用しているかどうかはわかりません。
- EFを単純に呼び出すものはテストしない - つまり、EFを直接呼び出すサービスメソッド(getAllなど)は、ユニットテストではなく、統合テストだけなのでしょうか?
どなたか、Repoを使わずに実際にやって成功した方はいらっしゃいますか?
解決方法は?
これは、私が非常に興味を持っているテーマです。EFやNHibernateのような技術をテストすべきではないと言う純粋主義者がたくさんいます。彼らは正しく、それらはすでに非常に厳しくテストされており、以前の回答が述べたように、所有していないものをテストするために膨大な時間を費やすことはしばしば無意味です。
しかし、その下のデータベースはあなたのものです。 EF/NHが正しく機能しているかどうかをテストする必要はないのです。マッピングや実装がデータベースと連動しているかどうかをテストする必要があるのです。私の意見では、これはシステムでテストできる最も重要な部分の1つです。
しかし、厳密に言えば、私たちは単体テストの領域を離れ、統合テストに移行しつつありますが、その原則は変わりません。
最初に必要なことは、BLLをEFやSQLから独立してテストできるように、DALをモック化することです。 これが単体テストです。 次に 統合テスト は、DALを証明するためのもので、私の意見では、これらは同じぐらい重要です。
いくつかありますね。
- データベースは、テストごとに既知の状態である必要があります。ほとんどのシステムは、バックアップを使用するか、このためのスクリプトを作成します。
- 各テストは繰り返し可能であること
- 各テストはアトミックでなければならない
データベースのセットアップには主に2つのアプローチがあり、1つはUnitTest create 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());
});
}
}
まとめると、何をテストしようとしているかに応じて、どちらのアプローチも有効です。
関連
-
[解決済み] [Solved] 1つ以上のエンティティで検証に失敗しました。詳細は'EntityValidationErrors'プロパティを参照してください [重複]。
-
[解決済み】エラー。「戻り値を変更できません」 C#
-
[解決済み] メンバー '<メンバー名>' にインスタンス参照でアクセスできない
-
[解決済み】WebForms UnobtrusiveValidationModeは、jqueryのScriptResourceMappingを必要とする
-
[解決済み】EF 5 Enable-Migrations : アセンブリにコンテキストタイプが見つかりませんでした
-
[解決済み】Entity FrameworkからのSqlException - セッション内で他のスレッドが動作しているため、新しいトランザクションは許可されません。
-
[解決済み】「...は'型'であり、与えられたコンテキストでは有効ではありません」を解決するにはどうすればよいですか?(C#)
-
[解決済み] 不変量名 'System.Data.SqlClient' を持つ ADO.NET プロバイダに対応する Entity Framework プロバイダが見つかりませんでした。
-
[解決済み] Entity Frameworkで生成されたSQLを表示するにはどうすればよいですか?
-
[解決済み] プライベートメソッドのユニットテストはどのように行うのですか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
[解決済み】"出力タイプがクラスライブラリのプロジェクトは直接起動できない"
-
[解決済み】GDI+、JPEG画像をMemoryStreamに変換する際にジェネリックエラーが発生しました。
-
[解決済み】C#で四捨五入する方法
-
[解決済み] [Entity Framework 4.1でエンティティに関連オブジェクトを追加する際に、エンティティオブジェクトをIEntityChangeTracker.の複数のインスタンスから参照できない。
-
[解決済み] 'SubSonic.Schema .DatabaseColumn' 型のオブジェクトをシリアライズする際に、循環参照が検出されました。
-
[解決済み】C# - パスに不正な文字がある場合
-
[解決済み] [Solved] 不正な文字列値: '\xEFxBFxBD' for column
-
[解決済み] UnityでOnCollisionEnterが呼ばれない
-
[解決済み】パラメータ付きRedirectToAction
-
[解決済み】別のスレッドがこのオブジェクトを所有しているため、呼び出し側のスレッドはこのオブジェクトにアクセスできない