对 LINQ2SQL 存储库进行单元测试

发布于 2024-07-16 02:43:49 字数 438 浏览 4 评论 0 原文

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

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

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

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

I am taking my first steps with MsTest and Moq and would like to unit test a Linq2SQL repository class. The problem is that I do not want the unit tests to permantly modify my development database.

Which would be the best approach for this scenario?

  • Let each test operate on my real development database, but make sure each test cleans up after itself
  • Create a duplicate of my development database and dbml for the unit test and use that context instead so I can clear the entire database before each test run
  • Find some elaborate way of mocking the Datacontext (please bear in mind that I am a total Moq noob).
  • Something completely different? Perhaps something that would automate setting up the database for me before each test run?

Edit: I just learned that MBUnit has a rollback attribute that reverses any database operations run by a test case. I am not particularly attached to MSTest, so could this be an easy answer to my problem?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

只是一片海 2024-07-23 02:43:49

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

我将在下面包含我的 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
}

I went with mocking/faking the database using some wrapper classes + a fake implementation based on http://andrewtokeley.net/archive/2008/07/06/mocking-linq-to-sql-datacontext.aspx. Note that I did end up implementing SubmitChanges logic in my fake data context wrapper to test out the validation logic in my entity's partial class implementation. I think that this was really the only tricky part which differed substantially from Tokeley's implementation.

I'll include my FakeDataContextWrapper implementation below:

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
}
七颜 2024-07-23 02:43:49

我有类似的需求 - 对 Linq to Sql 类进行单元测试,因此我创建了一小组类来将模拟数据上下文、ITables 和 IQueryables 放入查询中。

我将代码放在博客文章“Linq to Sql 的模拟和存根”。 它使用起订量,并且可以为您所进行的测试提供足够的功能,而无需访问数据库。

I had a similar need - to unit test the Linq to Sql classes, so I made a small set of classes to get mock datacontext, ITables and IQueryables into the queries.

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.

待天淡蓝洁白时 2024-07-23 02:43:49

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

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

这不是一个完美的解决方案,但我想我会使用 [ROLLBACK] 属性,因为它比较省力并且对我来说效果很好。

I played a bit with MBUnit and learned that, for most test cases, you can get away without mocking the datacontext by using MBUnit's [ROLLBACK] attribute.

Unfortunately there are also cases when the attribute produces strange side effects, such as loading a linq entity from the database, changing one property (without submitchanges), then loading the same entity again. Usually this results in no update query on the database, but from within the Test Method it appears as if the update is immediately executed as soon as I change the linq entity property.

Not a perfect solution, but I think I'll go with the [ROLLBACK] attribute since it's less effort and works well enough for me.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文