Модульное тестирование репозитория 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 и может обеспечить достаточную функциональность для тестов, которые вы используете, не попав в базу данных.