2016-04-04 3 views
1

私は(かなり)TDDを初めて使っていますので、やさしくしてください!ID列のモックデータベース

私はCodeFirstを使用して、VBS2015コミュニティのEF6を使用してC#でアプリケーションを作成しています。

データアクセスのために作成されたインターフェイスがあり、ユニットテストのためにメモリ内にモックデータベースを作成できます。

私のモデルの各ID(例えば、ユーザID)と命名された一意の識別子列があり

[OK]を、私は私のモックデータベースにレコードを追加するときに、私はそれがために新しいID値を作成してもらう必要があります私はこれを後のコードで参照しています。各テーブルに一意のID列がある場合、これを汎用セットでどのように行うのですか?私は独立して各テーブルをモックする必要がありますか?

私のデータベースインタフェースは、次のようになります。

Namespace Labinator2016.Lib.Headers 
{ 
    using System; 
    using System.Collections.Generic; 
    using System.Linq; 
    public interface ILabinatorDb : IDisposable 
    { 
     IQueryable<T> Query<T>() where T : class; 
     void Add<T>(T entity) where T : class; 
     void Delete<T1>(System.Linq.Expressions.Expression<Func<T1, bool>> target) where T1 : class; 
     void Remove<T>(T entity) where T : class; 
     void Update<T>(T entity) where T : class; 
     void SaveChanges(); 
    } 
} 

私のモックデータベースは、その後、次のようになります。

namespace Labinator2016.Tests.TestData 
{ 
    using System; 
    using System.Collections.Generic; 
    using System.Linq; 
    using Lib.Headers; 
    using Labinator2016.Lib.Models; 
    class FakeDatabase : ILabinatorDb 
    { 
     public IQueryable<T> Query<T>() where T : class 
     { 
      return this.Sets[typeof(T)] as IQueryable<T>; 
     } 
     public void Dispose() { } 
     public void AddSet<T>(IQueryable<T> objects) 
     { 
      this.Sets.Add(typeof(T), objects); 
     } 
     public void Add<T>(T entity) where T : class 
     { 
      this.Added.Add(entity); 
      IQueryable<T> temp = this.Sets[typeof(T)] as IQueryable<T>; 
      List<T> existing = temp.ToList(); 
      existing.Add(entity); 
      Sets[typeof(T)] = existing; 
     } 
     void ILabinatorDb.Delete<T1>(System.Linq.Expressions.Expression<Func<T1, bool>> Target) 
     { 
     } 
     public void Update<T>(T entity) where T : class 
     { 
      this.Updated.Add(entity); 
     } 
     public void Remove<T>(T entity) where T : class 
     { 
      this.Removed.Add(entity); 
     } 
     public void SaveChanges() 
     { 
      this.saved++; 
     } 
     public Dictionary<Type, object> Sets = new Dictionary<Type, object>(); 
     public List<object> Added = new List<object>(); 
     public List<object> Updated = new List<object>(); 
     public List<object> Removed = new List<object>(); 
     public int saved = 0; 
    } 
} 
現在

、私は、ユーザーテーブルからユーザーオブジェクトを取得するいくつかのコードを持っているベース名前で存在しない場合は、最初に作成されます。私のコードは以下の通りです:

User existingUser = this.db.Query<User>().Where(u => u.UserName == newUserName).FirstOrDefault(); 
if (existingUser == null) 
{ 
    User userToAdd = new User(); 
    userToAdd.UserName = newUserName; 
    userToAdd.Password = PasswordHash.CreateHash("password"); 
    this.db.Add<User>(userToAdd); 
    this.db.SaveChanges(); 
    existingUser = this.db.Query<User>().Where(u => u.EmailAddress == nu).FirstOrDefault(); 
} 

私のアプリケーションでは間違ったアプローチを使用していますか、この機能を模擬する方法はありますか?

答えて

0

あなたがテストしているものを正確に知らなくても、少し難しいと言えますが、Moqのようなモックフレームワークを使用して、あなたのために大変な作業をすることができます。以下は、新しいユーザーがデータベースに存在しない場合に作成されるかどうかをテストする方法と、そうでない場合(Moqを使用する方法)の簡単な例です。

public class UserManager 
{ 
    private readonly ILabinatorDb _liLabinatorDb; 

    public UserManager(ILabinatorDb liLabinatorDb) 
    { 
     _liLabinatorDb = liLabinatorDb; 
    } 

    public void CreateNewUser(string userName) 
    { 
     User existingUser = _liLabinatorDb.Query<User>().FirstOrDefault(u => u.UserName == userName); 

     if (existingUser == null) 
     { 
      User userToAdd = new User(); 
      userToAdd.UserName = userName; 

      _liLabinatorDb.Add<User>(userToAdd); 
      _liLabinatorDb.SaveChanges(); 
     } 
    } 
} 

[TestClass] 
public class UnitTest1 
{ 
    [TestMethod] 
    public void GivenAUsernameWhenNoUserExistsInDatabaseThenNewUserCreated() 
    { 
     var mockDatabase = new Mock<ILabinatorDb>(); 
     mockDatabase.Setup(a => a.Query<User>()).Returns(new List<User>().AsQueryable()); 
     var userManager = new UserManager(mockDatabase.Object); 
     userManager.CreateNewUser("UserDoesntExistInDatabase"); 

     mockDatabase.Verify(a => a.Add(It.IsAny<User>()),Times.Once); 
     mockDatabase.Verify(a => a.SaveChanges(),Times.Once); 
    } 

    [TestMethod] 
    public void GivenAUsernameWhenUserExistsInDatabaseThenNewUserNotCreated() 
    { 
     var mockDatabase = new Mock<ILabinatorDb>(); 
     var mockedUsers = new List<User> 
     { 
      new User {UserId = 2, UserName = "UserExistsInDatabase"} 
     }; 

     mockDatabase.Setup(a => a.Query<User>()).Returns(mockedUsers.AsQueryable()); 
     var userManager = new UserManager(mockDatabase.Object); 
     userManager.CreateNewUser("UserExistsInDatabase"); 

     mockDatabase.Verify(a => a.Add(It.IsAny<User>()), Times.Never); 
     mockDatabase.Verify(a => a.SaveChanges(), Times.Never); 
    } 
} 
0

それとも、右のフレームワーク(Typemock Isolator)を持つ新しいマネージャークラスを作成せずにそれを行うことができ、私は違いを示すために、最初の例を再現しました。それはより短く明確です:

[TestMethod, Isolated] 
public void TestAddWithEmptyDb() 
{ 
    //Arrange 
    var fakeDb = Isolate.Fake.AllInstances<FakeDatabase>(Members.CallOriginal); 
    var userList = new List<User>(); 
    Isolate.WhenCalled(() => fakeDb.Query<User>()).WillReturnCollectionValuesOf(userList.AsQueryable()); 

    //Act 
    UnderTest test = new UnderTest(); 
    test.get("newName"); 

    //Assert 
    Isolate.Verify.WasCalledWithArguments(() => fakeDb.Add<User>(null)).Matching(a => (a[0] as User).UserName == "newName"); 
    Isolate.Verify.WasCalledWithAnyArguments(() => fakeDb.SaveChanges()); 
} 
0

私たちのプロジェクトでは、同様の問題があります。私たちは、ロジックを使用してデータベースを嘲笑していない

  • これは、我々がに来た結論です。アイテムを追加した後は、クエリによって自動的に検索されません。 DB-Mockは、完全に機能するインメモリデータベースではありません。それぞれのメソッド呼び出しを個別にモックする必要があります(モックフレームワークを使用して)。これにより、テストフレームワークの複雑さが大幅に軽減されます。

  • 私たちにはが必要です。もデータベースに追加してIDを取得してください。 Idプロパティを任意の値で設定するデフォルトの模擬設定には、少しの反射コードがあります。私たちのIdプロパティはすべて、同じ名前を持つ。
  • 複雑なクエリをテストするような、ある種の実際のデータベースを必要とするロジックをテストするために、メモリ内のSQLiteデータベース(多かれ少なかれ透明性をサポートするNHibernateを使用)を使用します。
  • データベースのアクセスとクエリとは別のロジックをテストするために、インターフェイスの背後にある複雑なクエリを非表示にしてそれを疑似体験します。ディレクトリにアクセスする代わりに、GetUserByName(name)GetAllActiveUsers()などのメソッドを持つインターフェースを用意しています。