LinqTestableの新しいバージョンがリリースされました-ORMを介してデータベースクエリをテストするためのライブラリ

LinqTestableは、これら2つのパラダイムのNULL動作の違いから生じる、テストでのOOPとリレーショナルデータベース間の概念的なギャップを埋めるのに役立つライブラリです。 たとえば、NULL == NULLを比較すると、オブジェクト言語ではtrueが返され、リレーショナルモデルではfalseが返されます。 さらに、NULL.SomeFieldはリレーショナルモデルでNULLを返し、C#でNullReferenceExceptionをスローします。 LinqTestableは、この問題を解決するように設計されています。









ライブラリはまだ完全には準備ができていませんが、原則として既に使用することが可能です。 現在、OrderByの正しい処理に取り組んでいます。



現在準備ができているライブラリの機能を示すために、いくつかのユニットテストの例を示します。これらのテストはライブラリソースでも表示できます



NullRefenceExceptionの排除





次の例は、null.DOOR_IDが呼び出されたときにNullRefenceExceptionをスローする問題を解決します。 代わりに、nullは正しく返されます。

テストコード
[TestFixture] public class TwoLeftJoins { void ExecuteTwoLeftJoins(bool isSmart) { var dataModel = new TestDataModel {Settings = {IsSmart = isSmart}}; const int carId = 100; dataModel.CAR.AddObject(new CAR{CAR_ID = carId}); dataModel.CAR.AddObject(new CAR{CAR_ID = carId + 1}); var cars = (from car in dataModel.CAR join door in dataModel.DOOR on car.CAR_ID equals door.CAR_ID into joinedDoor from door in joinedDoor.DefaultIfEmpty() join doorHandle in dataModel.DOOR_HANDLE on door.DOOR_ID equals doorHandle.DOOR_ID into joinedDoorHandle from doorHandle in joinedDoorHandle.DefaultIfEmpty() select car).ToList(); Assert.AreEqual(2, cars.Count); Assert.AreEqual(carId, cars.First().CAR_ID); } [Test] public void TwoLeftJoinsShouldThrow() { Assert.Throws<NullReferenceException>(() => ExecuteTwoLeftJoins(false)); } [Test] public void SmartTwoLeftJoinsShouldNotThrow() { ExecuteTwoLeftJoins(true); } }
      
      









NullReferenceExceptionを防ぐために、コードに手動でnullチェックを追加できますが、この場合、元のSQLとは異なるSQLおよびクエリ実行プランを取得するリスクがあります。 これについては、このライブラリに関する以前の記事で書きました。



前のケースと同様のケースですが、今回は、NullableがValueにアクセスしたときにNullReferenceExceptionが防止されました。

テストコード
  [TestFixture] public class Contains { public void ExecuteContains(bool isSmart) { var dataModel = new TestDataModel { Settings = { IsSmart = isSmart } }; new[] { new DOOR_HANDLE {DOOR_HANDLE_ID = 1, MATERIAL_ID = 1}, new DOOR_HANDLE {DOOR_HANDLE_ID = 2, MATERIAL_ID = 2}, new DOOR_HANDLE {DOOR_HANDLE_ID = 3, MATERIAL_ID = null} } .ForEach(dataModel.DOOR_HANDLE.AddObject); var doorHandleIds = new List<int>{1,2}; var doorHandles = (from doorHandle in dataModel.DOOR_HANDLE where doorHandleIds.Contains(doorHandle.MATERIAL_ID.Value) select doorHandle).ToList(); Assert.AreEqual(2, doorHandles.Count); } [Test] public void ContainsShouldFail() { Assert.Throws<InvalidOperationException>(() => ExecuteContains(false)); } [Test] public void SmartContainsShouldSuccess() { ExecuteContains(true); } }
      
      









有効な行動の合計





Sumメソッドは、データベースとC#でデータのリストの上で異なる動作をします。 空のサンプルからの合計の場合、データベースはNULLを返し、C#では0を返します。合計の結果が渡されるフィールドがNullable <>でない場合、ORMは例外をスローします。 LinqTestableを使用する場合、データベースを使用しているかのように、SumはNULLを返すか例外をスローします。

テストコード
  [TestFixture] public class SumFromEmptyTable { void ExecuteSumFromEmptyTable(bool isSmart) { var dataModel = new TestDataModel {Settings = {IsSmart = isSmart}}; int sum = dataModel.CAR.Sum(x => x.CAR_ID); } [Test] public void SmartSumShouldThrow() { Assert.Throws<InvalidOperationException>(() => ExecuteSumFromEmptyTable(true)); } [Test] public void SumShouldNotThrow() { ExecuteSumFromEmptyTable(false); } void ExecuteNullableSumFromEmptyTable(bool isSmart) { var dataModel = new TestDataModel { Settings = { IsSmart = isSmart } }; int? sum = dataModel.DOOR_HANDLE.Sum(x => x.MATERIAL_ID); Assert.AreEqual(null, sum); } [Test] public void NullableSumShouldFail() { Assert.Throws<AssertionException>(() => ExecuteNullableSumFromEmptyTable(false)); } [Test] public void NullableSmartSumShouldSuccess() { ExecuteNullableSumFromEmptyTable(true); } }
      
      









null == nullの比較





LinqTestableを使用している場合、null == nullはfalseを意味します。ただし、データベースでクエリを実行する場合と同様に、クエリでnullとの比較を明示的に行わない限り:

テストコード
  [TestFixture] public class NullComparison { void ExecuteNullComparison(bool isSmart) { var dataModel = new TestDataModel { Settings = { IsSmart = isSmart } }; new[] { new DOOR_HANDLE {DOOR_HANDLE_ID = 1, MATERIAL_ID = 1, MANUFACTURER_ID = 1}, // <---- new DOOR_HANDLE {DOOR_HANDLE_ID = 2, MATERIAL_ID = 2, MANUFACTURER_ID = 2}, // |-- this is only pair new DOOR_HANDLE {DOOR_HANDLE_ID = 3, MATERIAL_ID = 1, MANUFACTURER_ID = 1}, // <---- new DOOR_HANDLE {DOOR_HANDLE_ID = 4, MATERIAL_ID = 5, MANUFACTURER_ID = null}, new DOOR_HANDLE {DOOR_HANDLE_ID = 5, MATERIAL_ID = 5, MANUFACTURER_ID = null}, new DOOR_HANDLE {DOOR_HANDLE_ID = 6, MATERIAL_ID = null, MANUFACTURER_ID = null}, new DOOR_HANDLE {DOOR_HANDLE_ID = 7, MATERIAL_ID = null, MANUFACTURER_ID = null} } .ForEach(x => dataModel.DOOR_HANDLE.AddObject(x)); var handlePairsWithSameMaterialAndManufacturer = (from handle in dataModel.DOOR_HANDLE join anotherHandle in dataModel.DOOR_HANDLE on handle.MATERIAL_ID equals anotherHandle.MATERIAL_ID where handle.MANUFACTURER_ID == anotherHandle.MANUFACTURER_ID && handle.DOOR_HANDLE_ID < anotherHandle.DOOR_HANDLE_ID select new {handle, anotherHandle}).ToList(); Assert.AreEqual(1, handlePairsWithSameMaterialAndManufacturer.Count); var pair = handlePairsWithSameMaterialAndManufacturer.First(); Assert.AreEqual(1, pair.handle.MATERIAL_ID); Assert.AreEqual(pair.handle.MATERIAL_ID, pair.anotherHandle.MATERIAL_ID); Assert.AreEqual(1, pair.handle.MANUFACTURER_ID); Assert.AreEqual(pair.handle.MANUFACTURER_ID, pair.anotherHandle.MANUFACTURER_ID); } [Test] public void NullComparisonShouldFail() { Assert.Throws<AssertionException>(() => ExecuteNullComparison(false)); } [Test] public void SmartNullComparisonShouldSuccess() { ExecuteNullComparison(true); } }
      
      









使用を開始する方法





このライブラリはフリーソフトウェアであり、「現状のまま」(保証なし)で提供されます。 Nugetからダウンロードできます。







ライブラリを接続したら、テストObjectSetのExpressionプロパティとProviderプロパティの実装を次のように置き換えます。



  public System.Linq.Expressions.Expression Expression { get { return _collection.AsQueryable<T>().ToTestable().Expression; } } public IQueryProvider Provider { get { return _collection.AsQueryable<T>().ToTestable().Provider; } }
      
      







LinqTestableを有効/無効にするスイッチを追加できます
  public Expression Expression { get { return _settings.IsSmart ? _collection.AsQueryable().ToTestable().Expression : _collection.AsQueryable().Expression; } } public IQueryProvider Provider { get { return _settings.IsSmart ? _collection.AsQueryable().ToTestable().Provider : _collection.AsQueryable().Provider; } }
      
      









ライブラリ自体のテストで、テストデータベースがどのように実装され、LinqTestableがそれに接続されているかの例を見ることができます。 ここにソースがあります



念のため 、ここに、Entity Frameworkを例として使用してテストデータベースを実装する方法に関する記事を示します。



現時点では、ライブラリは並べ替え(OrderBy)の操作方法を認識していません。近い将来修正される予定のマイナーな欠陥がいくつかあります。 またコードを少し変更します。



データベースとC#で動作が異なり、ライブラリで処理されないバグやケースを見つけた場合、問題のある単体テストをLinqTestable@mail.ruに送信していただければ幸いです。



All Articles