SQL データベースからすべてのデータを取得するために使用している Lin2Sql DataContext がありますが、関連する単体テストを作成できるように、これを正常にモックする方法を見つけるのに苦労しています。
テストしたいデータ アクセス オブジェクトでは、毎回コンテキストを更新していますが、これをモックする簡単で適切な方法を見つけるのが難しいと感じています。
この問題についての助けをいただければ幸いです。
SQL データベースからすべてのデータを取得するために使用している Lin2Sql DataContext がありますが、関連する単体テストを作成できるように、これを正常にモックする方法を見つけるのに苦労しています。
テストしたいデータ アクセス オブジェクトでは、毎回コンテキストを更新していますが、これをモックする簡単で適切な方法を見つけるのが難しいと感じています。
この問題についての助けをいただければ幸いです。
linq-to-sql コンテキストをモックすることは、実に大きな作業です。私は通常、単体テストに合わせて特別に作成されたデータを使用して、別のデータベース コピーに対して単体テストを実行できるようにすることで、この問題を回避しています。(もはや単体テストではなく、統合テストであると主張できることはわかっていますが、コードをテストする限り気にしません)。
データベースを既知の状態に保つためにTransactionScope
、テストの最後にロールバックされる で各テストをラップします。そうすれば、データベースの状態は決して変更されません。
サンプル テスト メソッドは次のようになります。
[TestMethod]
public void TestRetire()
{
using (TransactionScope transaction = new TransactionScope())
{
Assert.IsTrue(Car.Retire("VLV100"));
Assert.IsFalse(Car.Retire("VLV100"));
// Deliberately not commiting transaction.
}
}
コードは、私が以前に書いたメソッドに関するブログ投稿からのものです: http://coding.abel.nu/2011/12/using-transactions-for-unit-tests/
をモックする方法を要求したので、統合テストではなく、いくつかの単体テストDataContext
を本当に実行したいと思います。
さて、これを達成する方法を説明しますが、最初に次のリンクを読むことをお勧めします。これらはすべて、クリーンでテスト可能なコードを書くことに関するものです。
そして、この応答からのリンクを確認してください。
Misko Hevery によるクリーン コード トークをご覧ください (Google 社員に提供)
http://www.youtube.com/watch?v=wEhu57pih5w&feature=player_embedded
http://www.youtube.com/watch?v=RlfLCWKxHJ0&feature=player_embedded
http://www.youtube.com/watch?v=-FRm3VPhseI&feature=player_embedded
http://www.youtube.com/watch?v=4F72VULWFvc&feature=player_embedded
私が自分自身と職場の仲間に繰り返し言っていたことの 1 つは、ユニット テストは馬鹿げたほど簡単に記述できるため、誰でも記述できるということです。したがって、単純なテストは基本的に、いくつかの比較を行い、結果が失敗した場合に例外をスローするだけであり、誰でもそれを行うことができます。もちろん、これらのテストを洗練された方法で作成するのに役立つフレームワークは何百もあります。しかし、本当のこと、そして本当の努力は、クリーンでテスト可能なコードを書く方法を学ぶことに費やされるべきです
テストの作成を手伝うために Misko Hevery を雇ったとしても、コードがテストに適していなければ、彼はテストを書くのに本当に苦労するでしょう。
オブジェクトをモックする方法DataContext
は次のとおりです。実行しないでください
代わりに、代わりにカスタム インターフェイスを使用して呼び出しをラップします。
public interface IMyDataContextCalls
{
void Save();
IEnumerable<Product> GetOrders();
}
// this will be your DataContext wrapper
// this wll act as your domain repository
public class MyDataContextCalls : IMyDataContextCalls
{
public MyDataContextCalls(DataClasses1DataContext context)
{
this.Context = context;
}
public void Save()
{
this.Context.SubmitChanges();
}
public IEnumerable<Product> GetOrders()
{
// place here your query logic
return this.Context.Products.AsEnumerable();
}
private DataClasses1DataContext Context { get; set; }
}
// this will be your domain object
// this object will call your repository wrapping the DataContext
public class MyCommand
{
private IMyDataContextCalls myDataContext;
public MyCommand(IMyDataContextCalls myDataContext)
{
this.myDataContext = myDataContext;
}
public bool myDomainRule = true;
// assume this will be the SUT (Subject Under Test)
public void Save()
{
// some business logic
// this logic will be tested
if (this.myDomainRule == true)
{
this.myDataContext.Save();
}
else
{
// handle your domain validation errors
throw new InvalidOperationException();
}
}
}
[TestClass]
public class MyTestClass
{
[TestMethod]
public void MyTestMethod()
{
// in this test your mission is to test the logic inside the
// MyCommand.Save method
// create the mock, you could use a framework to auto mock it
// or create one manually
// manual example:
var m = new MyCommand(new MyFakeDataContextFake());
m.Invoking(x => x.Save())
//add here more asserts, maybe asserting that the internal
// state of your domain object was changed
// your focus is to test the logic of the domain object
.ShouldNotThrow();
//auto mock example:
var fix = new Fixture().Customize(new AutoMoqCustomization());
var sut = fix.CreateAnonymous<MyCommand>();
sut.myDomainRule = false;
sut.Invoking(x => x.Save())
.ShouldThrow<InvalidOperationException>();
}
public class MyFakeDataContextFake : IMyDataContextCalls
{
public void Save()
{
// do nothing, since you do not care in the logic of this method,
// remember your goal is to test the domain object logic
}
public IEnumerable<Product> GetOrders()
{
// we do not care on this right now because we are testing only the save method
throw new NotImplementedException();
}
}
}
ノート:
インターフェイスを宣言するIMyDataContextCalls
と、実際には a の使用が抽象化されますDataContext
。したがって、このインターフェイスには (ほとんどの場合) POCO オブジェクトのみを含める必要があります。このアプローチに従うと、インターフェイスは望ましくない依存関係から切り離されます。
特定のMyDataContextCalls
実装では、コンテキストを明示的に使用してDataClasses1DataContext
いますが、実装はいつでも自由に変更でき、外部コードには影響しません。これは、代わりに常にIMyDataContextCalls
インターフェイスを使用しているためです。したがって、いつでも、たとえばこの実装を、素晴らしいNHibernate =) または貧弱な ef またはモック 1を使用して別の実装に変更できます。
最後になりましたが、重要なことです。コードを再確認してください。ドメイン オブジェクトに演算子がないことがnew
わかります。これは、テストに適したコードを書くときの愚かなルールです。ドメイン オブジェクトの外部でオブジェクトを作成する責任を分離します。
私は個人的に、すべてのプロジェクトと作成するすべてのテストで 3 つのフレームワークを使用していますが、これらを本当にお勧めします。
たとえば、上記のコードでは、リポジトリの手動の偽物を作成する方法を示しましたが、これは実際のプロジェクトでは明らかにしたくないことです。テストを書きます。
代わりに、Moq と組み合わせた AutoFixture の機能を使用します。
この行:var m = new MyCommand(new MyFakeDataContextFake());
となります:
var fixture = new Fixture().Customize(new AutoMoqCustomization());
var sut = fixture.CreateAnonymous<MyCommand>();
それだけです。このコードは、 のコンストラクターで必要なすべてのオブジェクトのモックを自動的に作成しますMyCommand
。
つまり、DataContext をモックしません。そこからインターフェイスを抽出し、エンティティ セットのいくつかのコレクションを使用してそのインターフェイスをモックし、それらのコレクションの内容を確認します。