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

Издевательство Linq2Sql DataContext

У меня есть DataContext Lin2Sql, который я использую для получения всех моих данных из базы данных sql, но я изо всех сил пытаюсь найти способ успешно выполнить Mock, чтобы я мог создавать соответствующие тесты Unit.

В моих объектах доступа к данным, которые я хочу проверить, я каждый раз обновляю контекст, и мне трудно найти простой подходящий способ издеваться над этим.

Любая помощь в этом вопросе будет с благодарностью.

4b9b3361

Ответ 1

Отказывание контекста linq-to-sql действительно является огромной задачей. Я обычно работаю над этим, позволяя моим модульным тестам работать с отдельной копией базы данных, с данными, специально созданными для установки модульных тестов. (Я знаю, что можно утверждать, что это уже не модульные тесты, а тесты интеграции, но мне все равно, пока я тестирую код).

Чтобы сохранить базу данных в известном состоянии, я обертываю каждый тест в TransactionScope, который откатывается в конце теста. Таким образом, состояние базы данных никогда не изменяется.

Пример тестового метода выглядит следующим образом:

[TestMethod]
public void TestRetire()
{
    using (TransactionScope transaction = new TransactionScope())
    {
        Assert.IsTrue(Car.Retire("VLV100"));
        Assert.IsFalse(Car.Retire("VLV100"));

        // Deliberately not commiting transaction.
    }
}

Код из сообщения в блоге о методе, который я написал некоторое время назад: http://coding.abel.nu/2011/12/using-transactions-for-unit-tests/

Ответ 2

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

Хорошо, я расскажу вам, как это сделать, но сначала я хотел бы предложить вам прочитать следующие ссылки, все они о написании чистого тестового кода.

И проверьте ссылки из этого ответа:

Следите за чистым кодовым разговором от Misko Hevery (дается людям Google)

Одна вещь, которую я повторял себе и моим товарищам на работе, заключается в том, что любой может написать unit test, потому что они глупо писать по-писать. Таким образом, простой тест состоит, по существу, в том, чтобы делать некоторые сравнения и исключать исключения, если результаты не удались, каждый может это сделать. Конечно, есть сотни фреймворков, которые помогут нам написать эти тесты с помощью элегантного способа. Но реальная сделка, и реальный шарж усилий будет на научиться писать чистый проверяемый код

Даже если вы нанимаете Misko Hevery, чтобы помочь вам написать тесты, ему будет очень тяжело писать их, если ваш код не подходит для тестирования.

Теперь способ издеваться над объектами DataContext: не делать этого

Вместо этого оберните вызовы с помощью настраиваемого интерфейса:

public interface IMyDataContextCalls
{
    void Save();
    IEnumerable<Product> GetOrders();
}
// this will be your DataContext wrapper
// this wll act as your domain repository
public class MyDataContextCalls : IMyDataContextCalls
{
    public MyDataContextCalls(DataClasses1DataContext context)
    {
        this.Context = context;
    }

    public void Save()
    {
        this.Context.SubmitChanges();
    }

    public IEnumerable<Product> GetOrders()
    {
        // place here your query logic
        return this.Context.Products.AsEnumerable();
    }


    private DataClasses1DataContext Context { get; set; }

}

// this will be your domain object
// this object will call your repository wrapping the DataContext
public class MyCommand
{
    private IMyDataContextCalls myDataContext;
    public MyCommand(IMyDataContextCalls myDataContext)
    {
        this.myDataContext = myDataContext;
    }

    public bool myDomainRule = true;

    // assume this will be the SUT (Subject Under Test)
    public void Save()
    {
        // some business logic
        // this logic will be tested
        if (this.myDomainRule == true)
        {
            this.myDataContext.Save();
        }
        else
        {
            // handle your domain validation  errors
            throw new InvalidOperationException();
        }
    }
}

[TestClass]
public class MyTestClass
{
    [TestMethod]
    public void MyTestMethod()
    {
        // in this test your mission is to test the logic inside the 
        // MyCommand.Save method
        // create the mock, you could use a framework to auto mock it
        // or create one manually
        // manual example:
        var m = new MyCommand(new MyFakeDataContextFake());

        m.Invoking(x => x.Save())
            //add here more asserts, maybe asserting that the internal
            // state of your domain object was changed
            // your focus is to test the logic of the domain object
            .ShouldNotThrow();

        //auto mock example:
        var fix = new Fixture().Customize(new AutoMoqCustomization());
        var sut = fix.CreateAnonymous<MyCommand>();
        sut.myDomainRule = false;

        sut.Invoking(x => x.Save())
            .ShouldThrow<InvalidOperationException>();
    }

    public class MyFakeDataContextFake : IMyDataContextCalls
    {
        public void Save()
        {
            // do nothing, since you do not care in the logic of this method,
            // remember your goal is to test the domain object logic
        }

        public IEnumerable<Product> GetOrders()
        {
            // we do not care on this right now because we are testing only the save method

            throw new NotImplementedException();
        }
    }
}

Примечания:

  • Когда вы объявляете свой IMyDataContextCalls интерфейс, вы фактически абстрагируете использование DataContext, поэтому этот интерфейс должен содержать только объекты POCO (большую часть времени), если вы будете следовать этому подходу, ваши интерфейсы будут отделяется от любой нежелательной зависимости.

  • В конкретной реализации MyDataContextCalls вы явно используете контекст DataClasses1DataContext, но можете свободно изменять реализацию в любое время и что не влияет на внешний код. > , и это потому, что вы всегда работаете с интерфейсом IMyDataContextCalls. Поэтому в любое время вы можете изменить, например, эту реализацию для другого, используя замечательный NHibernate =) или плохой ef или макет.

  • Наконец, но не в последнюю очередь. дважды проверьте мой код, и вы заметите, что в объектах домена нет операторов new. Это правило немой при написании тестового дружественного кода: отмените ответственность за создание объектов за пределами объектов вашего домена.


Я лично использую три фреймворка для каждого проекта и при каждом тестировании, который я пишу, я действительно рекомендую:

Например, в приведенном выше коде я показал вам, как писать ручную фальшивку для вашего репозитория, но это явно то, что мы не хотим делать в реальном проекте, представьте себе количество объектов, которые вы должны были бы кодировать чтобы написать ваши тесты.

Вместо этого используйте силу AutoFixture в сочетании с Moq:

Эта строка: var m = new MyCommand(new MyFakeDataContextFake());

Станет:

        var fixture = new Fixture().Customize(new AutoMoqCustomization());
        var sut = fixture.CreateAnonymous<MyCommand>();

И что он, этот код будет автоматически создавать mocks для всех объектов, необходимых в конструкторе MyCommand.

Ответ 3

Короче говоря, вы не издеваетесь над DataContext. Вы извлекаете из него интерфейс и издеваетесь над этим интерфейсом, используя некоторые коллекции для наборов сущностей, а затем проверяете содержимое этих коллекций.