Модульное тестирование репозитория LINQ2SQL
Я делаю первые шаги с MsTest и Moq и хотел бы unit test класс репозитория Linq2SQL. Проблема заключается в том, что я не хочу, чтобы модульные тесты постоянно изменяли мою базу данных разработки.
Какой был бы лучший подход для этого сценария?
- Пусть каждый тест работает с моей реальной базой данных разработки, но убедитесь, что каждый тест очищается после себя
- Создайте дубликат моей базы данных разработки и dbml для unit test и используйте этот контекст вместо этого, чтобы я мог очистить всю базу данных до каждого тестового прогона
- Найдите сложный способ издеваться над Datacontext (помните, что я - полный Moq noob).
- Что-то совсем другое? Возможно, что-то, что автоматизирует настройку базы данных для меня перед каждым испытанием?
Изменить: Я только узнал, что MBUnit имеет атрибут отката, который отменяет любые операции с базой данных, выполняемые тестовым примером. Я не особенно привязан к MSTest, так может ли это быть легким ответом на мою проблему?
Ответы
Ответ 1
Я немного поиграл с MBUnit и узнал, что для большинства тестовых случаев вы можете уйти без издевательства над datacontext, используя атрибут MBUnit [ROLLBACK].
К сожалению, также есть случаи, когда атрибут вызывает странные побочные эффекты, такие как загрузка объекта linq из базы данных, изменение одного свойства (без подчиненных), а затем загрузка одного и того же объекта снова. Обычно это не приводит к отсутствию запроса на обновление в базе данных, но изнутри метода тестирования он выглядит так, как будто обновление немедленно выполняется сразу же после изменения свойства объекта linq.
Не идеальное решение, но я думаю, что я поеду с атрибутом [ROLLBACK], потому что это меньше усилий и работает достаточно хорошо для меня.
Ответ 2
Я пошел с насмешкой/фальсификацией базы данных, используя некоторые классы-оболочки + поддельную реализацию на основе 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
}
Ответ 3
У меня была такая же потребность - до unit test классов Linq to Sql, поэтому я сделал небольшой набор классов для получения в запросах mock datacontext, ITables и IQueryables.
Я помещаю код в сообщение в блоге " Mock и Stub для Linq to Sql". Он использует Moq и может обеспечить достаточную функциональность для тестов, которые вы используете, не попав в базу данных.