14

我正在使用 MsTest 和 Moq 迈出第一步,并希望对 Linq2SQL 存储库类进行单元测试。问题是我不希望单元测试永久修改我的开发数据库。

对于这种情况,哪种方法最好?

  • 让每个测试都在我的真实开发数据库上运行,但要确保每个测试都自行清理
  • 为单元测试创​​建我的开发数据库和 dbml 的副本,并改用该上下文,这样我就可以在每次测试运行之前清除整个数据库
  • 找到一些模拟 Datacontext 的复杂方法(请记住,我是一个完整的 Moq 菜鸟)。
  • 完全不同的东西?也许可以在每次测试运行之前自动为我设置数据库?

编辑:我刚刚了解到 MBUnit 有一个回滚属性,可以反转测试用例运行的任何数据库操作。我不是特别喜欢 MSTest,所以这可以简单地解决我的问题吗?

4

3 回答 3

14

我使用一些包装类 + 基于http://andrewtokeley.net/archive/2008/07/06/mocking-linq-to-sql-datacontext.aspx的假实现来模拟/伪造数据库。请注意,我最终在我的假数据上下文包装器中实现了 SubmitChanges 逻辑,以测试我实体的部分类实现中的验证逻辑。我认为这确实是与 Tokeley 的实现有很大不同的唯一棘手部分。

我将在下面包含我的 FakeDataContextWrapper 实现:

public class FakeDataContextWrapper : IDataContextWrapper
{

    public DataContext Context
    {
        get { return null; }
    }

    private List<object> Added = new List<object>();
    private List<object> Deleted = new List<object>();

    private readonly IFakeDatabase mockDatabase;

    public FakeDataContextWrapper( IFakeDatabase database )
    {
        mockDatabase = database;
    }

    protected List<T> InternalTable<T>() where T : class
    {
        return (List<T>)mockDatabase.Tables[typeof( T )];
    }

    #region IDataContextWrapper Members

    public virtual IQueryable<T> Table<T>() where T : class
    {
        return mockDatabase.GetTable<T>();
    }

    public virtual ITable Table( Type type )
    {
        return new FakeTable( mockDatabase.Tables[type], type );
    }

    public virtual void DeleteAllOnSubmit<T>( IEnumerable<T> entities ) where T : class
    {
        foreach (var entity in entities)
        {
            DeleteOnSubmit( entity );
        }
    }

    public virtual void DeleteOnSubmit<T>( T entity ) where T : class
    {
        this.Deleted.Add( entity );
    }

    public virtual void InsertAllOnSubmit<T>( IEnumerable<T> entities ) where T : class
    {
        foreach (var entity in entities)
        {
            InsertOnSubmit( entity );
        }
    }

    public virtual void InsertOnSubmit<T>( T entity ) where T : class
    {
        this.Added.Add( entity );
    }

    public virtual void SubmitChanges()
    {
        this.SubmitChanges( ConflictMode.FailOnFirstConflict );
    }

    public virtual void SubmitChanges( ConflictMode failureMode )
    {
        try
        {
            foreach (object obj in this.Added)
            {
                MethodInfo validator = obj.GetType().GetMethod( "OnValidate", BindingFlags.Instance | BindingFlags.NonPublic );
                if (validator != null)
                {

                    validator.Invoke( obj, new object[] { ChangeAction.Insert } );
                }
                this.mockDatabase.Tables[obj.GetType()].Add( obj );
            }

            this.Added.Clear();

            foreach (object obj in this.Deleted)
            {
                MethodInfo validator = obj.GetType().GetMethod( "OnValidate", BindingFlags.Instance | BindingFlags.NonPublic );
                if (validator != null)
                {
                    validator.Invoke( obj, new object[] { ChangeAction.Delete } );
                }
                this.mockDatabase.Tables[obj.GetType()].Remove( obj );
            }

            this.Deleted.Clear();

            foreach (KeyValuePair<Type, IList> tablePair in this.mockDatabase.Tables)
            {
                MethodInfo validator = tablePair.Key.GetMethod( "OnValidate", BindingFlags.Instance | BindingFlags.NonPublic );
                if (validator != null)
                {
                    foreach (object obj in tablePair.Value)
                    {
                        validator.Invoke( obj, new object[] { ChangeAction.Update } );
                    }
                }
            }
        }
        catch (TargetInvocationException e)
        {
            throw e.InnerException;
        }
    }

    public void Dispose() { }

    #endregion
}
于 2009-04-07T17:37:44.960 回答
2

我也有类似的需求——对 Linq to Sql 类进行单元测试,所以我制作了一小部分类来将模拟数据上下文、ITables 和 IQueryables 放入查询中。

I put the code in a blog post "Mock and Stub for Linq to Sql". It uses Moq, and might provide enough functionality for the tests you're after without hitting the database.

于 2009-04-29T12:46:19.087 回答
1

我玩了一点 MBUnit 并了解到,对于大多数测试用例,您可以通过使用 MBUnit 的 [ROLLBACK] 属性而无需模拟数据上下文。

不幸的是,在某些情况下,属性会产生奇怪的副作用,例如从数据库加载 linq 实体,更改一个属性(没有提交更改),然后再次加载相同的实体。通常这会导致数据库上没有更新查询,但是在测试方法中,一旦我更改了 linq 实体属性,就会立即执行更新。

不是一个完美的解决方案,但我认为我会使用 [ROLLBACK] 属性,因为它的工作量更少并且对我来说效果很好。

于 2009-04-09T15:39:53.097 回答