Подтвердить что ты не робот

Групповое тестирование с помощью EF4 "Code First" и репозитория

Я пытаюсь получить дескриптор тестирования модуля очень простого тестового приложения ASP.NET MVC, которое я создал с использованием подхода Code First в последнем CTP EF4. Я не очень разбираюсь в модульном тестировании/издевательстве и т.д.

Это мой класс репозитория:

public class WeightTrackerRepository
{
    public WeightTrackerRepository()
    {
        _context = new WeightTrackerContext();
    }

    public WeightTrackerRepository(IWeightTrackerContext context)
    {
        _context = context;
    }

    IWeightTrackerContext _context;

    public List<WeightEntry> GetAllWeightEntries()
    {
        return _context.WeightEntries.ToList();
    }

    public WeightEntry AddWeightEntry(WeightEntry entry)
    {
        _context.WeightEntries.Add(entry);
        _context.SaveChanges();
        return entry;
    }
}

Это IWeightTrackerContext

public interface IWeightTrackerContext
{
    DbSet<WeightEntry> WeightEntries { get; set; }
    int SaveChanges();
}

... и его реализация, WeightTrackerContext

public class WeightTrackerContext : DbContext, IWeightTrackerContext
{
    public DbSet<WeightEntry> WeightEntries { get; set; }
}

В моем тесте у меня есть следующее:

[TestMethod]
public void Get_All_Weight_Entries_Returns_All_Weight_Entries()
{
    // Arrange
    WeightTrackerRepository repos = new WeightTrackerRepository(new MockWeightTrackerContext());

    // Act
    List<WeightEntry> entries = repos.GetAllWeightEntries();

    // Assert
    Assert.AreEqual(5, entries.Count);
}

И мой MockWeightTrackerContext:

class MockWeightTrackerContext : IWeightTrackerContext
{
    public MockWeightTrackerContext()
    {
        WeightEntries = new DbSet<WeightEntry>();
        WeightEntries.Add(new WeightEntry() { Date = DateTime.Parse("01/06/2010"), Id = 1, WeightInGrams = 11200 });
        WeightEntries.Add(new WeightEntry() { Date = DateTime.Parse("08/06/2010"), Id = 2, WeightInGrams = 11150 });
        WeightEntries.Add(new WeightEntry() { Date = DateTime.Parse("15/06/2010"), Id = 3, WeightInGrams = 11120 });
        WeightEntries.Add(new WeightEntry() { Date = DateTime.Parse("22/06/2010"), Id = 4, WeightInGrams = 11100 });
        WeightEntries.Add(new WeightEntry() { Date = DateTime.Parse("29/06/2010"), Id = 5, WeightInGrams = 11080 });
    }

    public DbSet<WeightEntry> WeightEntries { get;set; }

    public int SaveChanges()
    {
        throw new NotImplementedException();
    }
}

Моя проблема возникает, когда я пытаюсь создать некоторые тестовые данные, поскольку я не могу создать DbSet<>, поскольку он не имеет конструктора. У меня такое чувство, что я лаяю не то дерево, весь мой подход пытается издеваться над моим контекстом. Любые советы были бы наиболее приветствуемыми для этого полнофункционального тестирующего новичка.

4b9b3361

Ответ 1

Вы создаете DbSet через метод Factory Set() в Контексте, но вы не хотите, чтобы какая-либо зависимость от EF в вашем unit test. Таким образом, вам нужно взглянуть на то, чтобы реализовать заглушку DbSet, используя интерфейс IDbSet или Stub, используя одну из фонов Mocking, таких как Moq или RhinoMock. Предполагая, что вы написали свой собственный Stub, вы просто добавляете объекты WeightEntry к внутреннему hashset.

Возможно, вам удастся узнать об модульном тестировании EF, если вы ищете ObjectSet и IObjectSet. Они являются аналогами DbSet перед первым выпуском CTP кода и более подробно описывают их с точки зрения модульного тестирования.

Здесь отличная статья в MSDN, где обсуждается возможность проверки кода EF. Он использует IObjectSet, но я думаю, что это все еще актуально.

Как ответ на комментарий Дэвида, я добавляю это добавление ниже, так как оно не вписывается в комментарии. Не уверен, что это лучшая практика для комментариев с длинными комментариями?

Вы должны изменить интерфейс IWeightTrackerContext, чтобы вернуть IDbSet из свойства WeightEntries, а не конкретный тип DbSet. Затем вы можете создать MockContext либо с насмешливой структурой (рекомендуется), либо с вашим собственным заглушкой. Это вернет StubDbSet из свойства WeightEntries.

Теперь у вас также будет код (например, пользовательские репозитории), которые зависят от IWeightTrackerContext, который в процессе производства вы передадите в свой Entity Framework WeightTrackerContext, который будет реализовывать IWeightTrackerContext. Это, как правило, осуществляется посредством инъекции конструктора с использованием структуры IoC, такой как Unity. Для тестирования кода репозитория, который зависит от EF, вы должны пройти в своей реализации MockContext, поэтому тестируемый код думает, что он разговаривает с "реальным" EF и базой данных и ведет себя (надеюсь), как и ожидалось. Поскольку вы удалили зависимость от изменяемой внешней системы db и EF, вы можете затем надежно проверять вызовы репозитория в своих модульных тестах.

Большая часть издевательских фреймворков обеспечивает возможность проверки вызовов объектов Mock для проверки поведения. В вашем примере выше ваш тест на самом деле проверяет только функциональность DbSet Add, которая не должна вас беспокоить, поскольку MS будет иметь модульные тесты для этого. То, что вы хотели бы знать, это то, что вызов Add on DbSet был сделан из вашего собственного кода репозитория, если это необходимо, и именно там появляются фреймворки Mock.

Извините, я знаю, что это много, чтобы переварить, но если вы прочитаете эту статью, многое станет более ясным, так как Скотт Аллен намного лучше объясняет этот материал, чем я:)

Ответ 2

Основываясь на том, что сказал Даз (и, как я уже упоминал в его комментариях), вам нужно будет сделать "поддельную" реализацию IDbSet. Пример для CTP 4 можно найти здесь, но чтобы заставить его работать, вам нужно будет настроить метод Find и добавить возвращаемые значения для пары ранее недействительных void- ed, например Add.

Ниже приведен мой собственный пример для CTP 5:

public class InMemoryDbSet<T> : IDbSet<T> where T : class
{
    readonly HashSet<T> _data;
    readonly IQueryable _query;

    public InMemoryDbSet()
    {
        _data = new HashSet<T>();
        _query = _data.AsQueryable();
    }

    public T Add(T entity)
    {
        _data.Add(entity);
        return entity;
    }

    public T Attach(T entity)
    {
        _data.Add(entity);
        return entity;
    }

    public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, T
    {
        throw new NotImplementedException();
    }

    public T Create()
    {
        return Activator.CreateInstance<T>();
    }

    public virtual T Find(params object[] keyValues)
    {
        throw new NotImplementedException("Derive from FakeDbSet and override Find");
    }

    public System.Collections.ObjectModel.ObservableCollection<T> Local
    {
        get { return new System.Collections.ObjectModel.ObservableCollection<T>(_data); }
    }

    public T Remove(T entity)
    {
        _data.Remove(entity);
        return entity;
    }

    public IEnumerator<T> GetEnumerator()
    {
        return _data.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return _data.GetEnumerator();
    }

    public Type ElementType
    {
        get { return _query.ElementType; }
    }

    public Expression Expression
    {
        get { return _query.Expression; }
    }

    public IQueryProvider Provider
    {
        get { return _query.Provider; }
    }
}

Ответ 3

Вероятно, вы получите сообщение об ошибке

 AutoFixture was unable to create an instance from System.Data.Entity.DbSet`1[...], most likely because it has no public constructor, is an abstract or non-public type.

У DbSet есть защищенный конструктор.

Подход, обычно используемый для поддержки тестируемости, создает вашу собственную реализацию Db Set

public class MyDbSet<T> : IDbSet<T> where T : class
{

Используйте это как

public class MyContext : DbContext
{
    public virtual MyDbSet<Price> Prices { get; set; }
}

Если вы посмотрите на следующий вопрос SO, ответ 2ns, вы сможете найти полную реализацию настраиваемого DbSet.

Единичное тестирование с помощью EF4 "Code First" и хранилище

Тогда

 var priceService = fixture.Create<PriceService>();

Не следует создавать исключение.