Модульное тестирование репозитория LINQ2SQL - PullRequest
14 голосов
/ 07 апреля 2009

Я делаю первые шаги с помощью MsTest и Moq и хотел бы провести модульное тестирование класса репозитория Linq2SQL. Проблема в том, что я не хочу, чтобы модульные тесты постоянно модифицировали мою базу данных разработки.

Какой подход лучше всего подходит для этого сценария?

  • Пусть каждый тест работает с моей реальной базой данных разработки, но убедитесь, что каждый тест очищает после себя
  • Создайте дубликат моей базы данных разработки и dbml для модульного теста и используйте вместо этого этот контекст, чтобы я мог очистить всю базу данных перед каждым запуском теста
  • Найдите какой-нибудь изощренный способ насмешки над текстом данных (имейте в виду, что я полный мук нуб).
  • Что-то совершенно другое? Возможно, что-то, что автоматизировало бы настройку базы данных для меня перед каждым тестом?

Редактировать: Я только что узнал, что MBUnit имеет атрибут отката, который отменяет все операции базы данных, выполняемые тестовым примером. Я не особо привязан к MSTest, так что это может быть простым ответом на мою проблему?

Ответы [ 3 ]

14 голосов
/ 07 апреля 2009

Я пошел на макетирование / подделку базы данных с использованием некоторых классов-оболочек + ложную реализацию, основанную на 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
}
2 голосов
/ 29 апреля 2009

У меня была похожая потребность - провести модульное тестирование классов Linq to Sql, поэтому я создал небольшой набор классов, чтобы включить в запросы фиктивный текст данных, ITables и IQueryables.

Я разместил код в сообщении в блоге " Макет и заглушка для Linq to Sql ". Он использует Moq и может предоставить достаточно функциональности для тех тестов, которые вы проводите, не обращаясь к базе данных.

1 голос
/ 09 апреля 2009

Я немного поиграл с MBUnit и узнал, что для большинства тестовых случаев вы можете обойтись без насмешки над текстовым данным, используя атрибут MBUnit [ROLLBACK].

К сожалению, есть также случаи, когда атрибут вызывает странные побочные эффекты, такие как загрузка сущности linq из базы данных, изменение одного свойства (без submitchanges), а затем загрузка этой же сущности снова. Обычно это приводит к тому, что запрос к обновлению базы данных не выполняется, но изнутри метода тестирования создается впечатление, что обновление выполняется немедленно, как только я изменяю свойство сущности linq.

Не идеальное решение, но я думаю, что я пойду с атрибутом [ROLLBACK], так как он требует меньше усилий и работает достаточно хорошо для меня.

...