Использование одного и того же набора тестов в различных реализациях интерфейса репозитория.
-
09-06-2019 - |
Вопрос
Я создавал небольшое игрушечное веб-приложение на C# по образцу витрины Asp.net MVC Роба Коннери.
Я обнаружил, что у меня есть интерфейс репозитория, назовите его IFooRepository, с методами, скажем,
IQueryable<Foo> GetFoo();
void PersistFoo(Foo foo);
И у меня есть три реализации этого:ISqlFooRepository, IFileFooRepostory и IMockFooRepository.
У меня также есть несколько тестовых примеров.Что я хотел бы сделать, но еще не придумал, как это сделать, — это запустить одни и те же тестовые примеры для каждой из этих трех реализаций и поставить зеленую галочку для каждого прохода теста для каждого типа интерфейса.
например
[TestMethod]
Public void GetFoo_NotNull_Test()
{
IFooRepository repository = GetRepository();
var results = repository. GetFoo();
Assert.IsNotNull(results);
}
Я хочу, чтобы этот тестовый метод запускался три раза с некоторыми изменениями в среде, позволяющими получить три разных типа репозитория.В настоящее время у меня есть три вырезанных и вставленных тестовых класса, которые отличаются только реализацией частного вспомогательного метода IFooRepository GetRepository();Понятно, что это вонючее.
Однако я не могу просто удалить дублирование, объединив методы вырезания и вставки, поскольку для запуска теста они должны присутствовать, быть общедоступными и помечены как тестовые.
Я использую среду тестирования Microsoft и предпочел бы остаться с ней, если смогу.Но предложение о том, как это сделать, скажем, в MBUnit, также будет представлять некоторый интерес.
Решение
Создайте абстрактный класс, содержащий конкретные версии тестов и абстрактный метод GetRepository, который возвращает IFooRepository.Создайте три класса, производные от абстрактного класса, каждый из которых реализует GetRepository таким образом, чтобы возвращать соответствующую реализацию IFooRepository.Добавьте все три класса в свой набор тестов, и все готово.
Чтобы иметь возможность выборочно запускать тесты для одних поставщиков, а не для других, рассмотрите возможность использования атрибута MbUnit «[FixtureCategory]» для категоризации ваших тестов — предлагаемые категории: «быстрый», «медленный», «db», «важный» и «неважный» ( Последние два - шутки - честно!)
Другие советы
В MbUnit вы можете использовать атрибут RowTest для указания параметров вашего теста.
[RowTest]
[Row(new ThisRepository())]
[Row(new ThatRepository())]
Public void GetFoo_NotNull_Test(IFooRepository repository)
{
var results = repository.GetFoo();
Assert.IsNotNull(results);
}
Если у вас есть три метода копирования и вставки, вы сможете провести их рефакторинг (извлечение метода), чтобы избавиться от дублирования.
то естьвот что я имел в виду:
private IRepository GetRepository(RepositoryType repositoryType)
{
switch (repositoryType)
{
case RepositoryType.Sql:
// return a SQL repository
case RepositoryType.Mock:
// return a mock repository
// etc
}
}
private void TestGetFooNotNull(RepositoryType repositoryType)
{
IFooRepository repository = GetRepository(repositoryType);
var results = repository.GetFoo();
Assert.IsNotNull(results);
}
[TestMethod]
public void GetFoo_NotNull_Sql()
{
this.TestGetFooNotNull(RepositoryType.Sql);
}
[TestMethod]
public void GetFoo_NotNull_File()
{
this.TestGetFooNotNull(RepositoryType.File);
}
[TestMethod]
public void GetFoo_NotNull_Mock()
{
this.TestGetFooNotNull(RepositoryType.Mock);
}
[TestMethod]
public void GetFoo_NotNull_Test_ForFile()
{
GetFoo_NotNull(new FileRepository().GetRepository());
}
[TestMethod]
public void GetFoo_NotNull_Test_ForSql()
{
GetFoo_NotNull(new SqlRepository().GetRepository());
}
private void GetFoo_NotNull(IFooRepository repository)
{
var results = repository. GetFoo();
Assert.IsNotNull(results);
}
Подводя итог, можно сказать, что есть три пути:
1) Сделайте однострочные тесты, которые вызывают общие методы (ответ Рика, также Халлгрима)
2) Используйте функцию RowTest MBUnit для автоматизации этого (ответ Джона Лимджапа).Я бы также использовал здесь перечисление, например.
[RowTest]
[Row(RepositoryType.Sql)]
[Row(RepositoryType.Mock)]
public void TestGetFooNotNull(RepositoryType repositoryType)
{
IFooRepository repository = GetRepository(repositoryType);
var results = repository.GetFoo();
Assert.IsNotNull(results);
}
3) Используйте базовый класс, ответ belugabob
Я сделал образец на основе этой идеи
public abstract class TestBase
{
protected int foo = 0;
[TestMethod]
public void TestUnderTen()
{
Assert.IsTrue(foo < 10);
}
[TestMethod]
public void TestOver2()
{
Assert.IsTrue(foo > 2);
}
}
[TestClass]
public class TestA: TestBase
{
public TestA()
{
foo = 4;
}
}
[TestClass]
public class TestB: TestBase
{
public TestB()
{
foo = 6;
}
}
В результате получается четыре пройденных теста в двух тестовых классах.
Плюсы 3:
1) Минимум дополнительного кода, минимум обслуживания.
2) Меньше всего набора текста для подключения нового репозитория, если это необходимо - это будет сделано в одном месте, в отличие от других.
Минусы:
1) Меньшая гибкость, позволяющая не проводить тестирование провайдера, если это необходимо.
2) Труднее читать.