我正在使用MsTest和Moq的第一步,并希望对Linq2SQL存储库类进行单元测试。问题是我不希望单元测试永久修改我的开发数据库。
对于这种情况,哪种方法最好?
编辑:我刚刚了解到MBUnit有一个rollback属性可以反转测试用例运行的所有数据库操作。我对MSTest并不特别感兴趣,所以这可以很容易地解决我的问题吗?
答案 0 :(得分: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
}
答案 1 :(得分:2)
我有类似的需求 - 对Linq to Sql类进行单元测试,所以我创建了一小组类来将mock datacontext,ITables和IQueryables放入查询中。
我将代码放在博客文章“Mock and Stub for Linq to Sql”中。它使用Moq,并且可以为您所进行的测试提供足够的功能而无需访问数据库。
答案 2 :(得分:1)
我在MBUnit上玩了一下,并了解到,对于大多数测试用例,你可以通过使用MBUnit的[ROLLBACK]属性而不用模拟datacontext来逃脱。
不幸的是,也存在属性产生奇怪副作用的情况,例如从数据库加载linq实体,更改一个属性(没有submitchanges),然后再次加载同一个实体。通常这会导致数据库上没有更新查询,但是从测试方法中看起来就像更改linq实体属性时立即执行更新一样。
不是一个完美的解决方案,但我认为我会使用[ROLLBACK]属性,因为它不那么省力,对我来说效果还不错。