Как обеспечить, чтобы очистка базы данных всегда выполнялась после теста?

Рассмотрим следующий пример unit test. Комментарии в значительной степени объясняют мою проблему.

[TestMethod]
public void MyTestMethod()
{

  //generate some objects in the database
  ...

  //make an assert that fails sometimes (for example purposes, this fails always)
  Assert.IsTrue(false);

  //TODO: how do we clean up the data generated in the database now that the test has ended here?

}

Ответы

Ответ 1

Есть два способа сделать это. Один из них использует атрибуты TestInitialize и TestCleanup для методов в тестовом классе. Они всегда будут выполняться до и после теста, соответственно.

Другим способом является использование того факта, что ошибки тестирования распространяются на тестовый бегун через исключения. Это означает, что блок try {} finally {} в вашем тесте можно использовать, чтобы очистить что-либо после неудачи assert.

[TestMethod]
public void FooTest()
{
  try
  {
     // setup some database objects
     Foo foo = new Foo();
     Bar bar = new Bar(foo);
     Assert.Fail();
  }
  finally
  {
     // remove database objects.
  }
}

Очистка try/finally может стать действительно запутанной, есть много объектов для очистки. То, к чему моя команда склонилась, - это вспомогательный класс, который реализует IDisposable. Он отслеживает, какие объекты были созданы и толкает их в стек. Когда Dispose вызывается, элементы выгружаются из стека и удаляются из базы данных.

[TestMethod]
public void FooTest()
{
  using (FooBarDatabaseContext context = new FooBarDatabaseContext())
  {
    // setup some db objects.
    Foo foo = context.NewFoo();
    Bar bar = context.NewBar(foo);
    Assert.Fail();
  } // calls dispose. deletes bar, then foo.
}

Это имеет дополнительное преимущество для переноса конструкторов в вызовы методов. Если изменения сигнатур конструктора, мы можем легко изменить тестовый код.

Ответ 2

Я думаю, что лучший ответ в подобных ситуациях - это очень внимательно подумать о том, что вы пытаетесь проверить. В идеале unit test должен пытаться проверить один факт об одном методе или функции. Когда вы начинаете комбинировать много вещей вместе, он переходит в мир интеграционных тестов (которые одинаково ценны, но различны).

Для тестирования модулей, чтобы вы могли тестировать только те объекты, которые хотите протестировать, вам потребуется дизайн для проверки. Обычно это связано с дополнительным использованием интерфейсов (я предполагаю, что .NET из кода, который вы показывали) и некоторой формой инъекции зависимостей (но не требует контейнера IoC/DI, если вы этого не хотите). Это также дает преимущества и поощряет создание в вашей системе очень сплоченных (одноцелевых) и развязанных (мягких зависимостей) классов.

Итак, когда вы тестируете бизнес-логику, которая зависит от данных из базы данных, вы обычно используете что-то вроде Repository Pattern и вводите fake/stub/mock IXXXRepository для модульного тестирования. Когда вы тестируете конкретный репозиторий, вам необходимо либо выполнить очистку базы данных, о которой вы спрашиваете, либо вам нужно прошить или заглушить базовый вызов базы данных. Это действительно зависит от вас.

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

В MS-Test атрибуты, которые вы должны использовать для объявления установки/удаления, ClassInitialize, ClassCleanUp, TestInitialize, TestCleanUp. Другие структуры имеют аналогичные конструкции.

Существует ряд фреймворков, которые могут помочь вам с насмешкой /stubbing: Moq, Rhino Mocks, NMock, TypeMock, Moles and Stubs (VS2010), VS11 Fakes (VS11 Beta) и т.д. Если вы ищете рамки для внедрения зависимостей, посмотрите на такие вещи, как Ninject, Unity, Замок Виндзор, и др.

Ответ 3

Несколько ответов:

  • Если он использует фактическую базу данных, я бы сказал, что это не "unit test" в строгом смысле этого слова. Это интеграционный тест. A unit test не должен иметь таких побочных эффектов. Подумайте, используя насмешливую библиотеку для имитации реальной базы данных. Rhino Mocks является одним, но есть много других.

  • Если, однако, вся суть этого теста состоит в том, чтобы фактически взаимодействовать с базой данных, тогда вы захотите взаимодействовать с временной тестовой базой данных. В этом случае часть вашего автоматизированного тестирования будет включать в себя код для создания тестовой базы данных с нуля, затем выполнить тесты, а затем уничтожить тестовую базу данных. Опять же, идея заключается в отсутствии внешних побочных эффектов. Вероятно, есть несколько способов сделать это, и я недостаточно разбираюсь в модулях тестирования модулей, чтобы действительно дать конкретное предложение. Но если вы используете тестирование, которое было встроено в Visual Studio, возможно, был бы полезен Project Database Database.

    /li >

Ответ 4

Ваш вопрос немного слишком общий. Обычно вы должны очищаться после каждого теста. Обычно вы не можете полагаться на то, что все тесты всегда выполняются в одном порядке, и вы должны быть уверены в том, что находится в вашей базе данных. Для общей настройки или очистки большинство unit test фреймворков предоставляют методы setUp и tearDown, которые вы можете переопределить и будут автоматически вызваны. Я не знаю, как это работает в С#, но e. г. в JUnit (Java) у вас есть эти методы.

Я согласен с Дэвидом. Обычно ваши тесты не должны иметь побочных эффектов. Вы должны создать новую базу данных для каждого отдельного теста.

Ответ 5

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

Альтернативой является использование инструментов Mocking, таких как Rhino Mocks, чтобы база данных была только в базе данных памяти.

Ответ 6

Это зависит от того, что вы на самом деле испытываете. Глядя на комментарии, я бы сказал "да", но, кстати, трудно удержать просмотр комментариев. Очистка объекта, который вы только что вставили, на практике, reset состояние теста. Поэтому, если вы очистите, вы начнете тестировать систему очистки.

Ответ 7

Я думаю, что очистка зависит от того, как вы создаете данные, поэтому, если "старые тестовые данные" не взаимодействуют с будущими прогонами тестов, я думаю, что это нормально, чтобы оставить его.

Подход, который я принимал при написании интеграционных тестов, заключается в том, чтобы тесты выполнялись с другим db, чем с db приложения. Я стараюсь перестроить тест db как предварительное условие для каждого тестового прогона. Таким образом, вам не нужна гранулированная схема очистки для каждого теста, так как каждый тестовый прогон получает чистый список между прогонами. Большая часть моего развития я сделал с использованием SQL-сервера, но в некоторых случаях я запускаю свои тесты против SQL Compact edition db, который быстро и эффективно восстанавливает между прогонами.

Ответ 8

mbUnit имеет очень удобный атрибут Rollback, который очищает базу данных после завершения теста. Однако вам нужно настроить DTC (Distributed Transaction Coordinator), чтобы иметь возможность использовать его.

Ответ 9

У меня была сопоставимая проблема, когда одно утверждение теста предотвращало очистку и вызывало другие тесты.

Надеюсь, это когда-нибудь кому-то понадобится.

    [Test]
    public void Collates_Blah_As_Blah()
    {
        Assert.False(SINGLETON.Collection.Any());

        for (int i = 0; i < 2; i++)
            Assert.That(PROCESS(ValidRequest) == Status.Success);

        try
        {
            Assert.AreEqual(1, SINGLETON.Collection.Count);
        }
        finally
        {
            SINGLETON.Collection.Clear();
        }
    }

Блок finally выполнит, будет ли утверждение проходить или терпит неудачу, оно также не вводит риск ложных проходов, которые будут вызывать!