Могу ли я реализовать серию повторных тестов для тестирования реализации интерфейса?
Я пишу серию классов коллекции в С#, каждая из которых реализует аналогичные пользовательские интерфейсы. Можно ли написать единый набор модульных тестов для интерфейса и автоматически запустить их все на нескольких разных реализациях? Я бы хотел избежать дублирования кода тестирования для каждой реализации.
Я хочу заглянуть в любую инфраструктуру (NUnit и т.д.) или расширение Visual Studio, чтобы выполнить это.
Для тех, кто хочет сделать то же самое, я опубликовал свое конкретное решение, основанное на принятом решении avandeursen, в качестве ответить.
Ответы
Ответ 1
Да, это возможно. Хитрость заключается в том, чтобы позволить иерархической иерархии вашего класса следовать иерархии классов вашего кода.
Предположим, что у вас есть интерфейс Itf
с реализацией классов C1
и C2
.
Сначала создайте тестовый класс для Itf
(ItfTest
). Чтобы на самом деле провести тест, вам нужно создать макетную реализацию вашего интерфейса Itf
.
Все тесты в этом ItfTest
должны проходить любую реализацию Itf
(!). Если нет, ваша реализация не соответствует Принципу замены Лискова ( "L" в Martin SOLID принципы проектирования OO)
Таким образом, чтобы создать тестовый пример для C1
, ваш класс C1Test
может расширить ItfTest
. Ваше расширение должно заменить создание mock-объекта созданием объекта C1
(ввод его или использование GoF factory method). Таким образом, все случаи ItfTest
применяются к экземплярам типа C1
. Кроме того, ваш класс C1Test
может содержать дополнительные тестовые примеры, относящиеся к C1
.
Аналогично для C2
. И вы можете повторить трюк для более глубоких вложенных классов и интерфейсов.
Ссылки: Binder Полиморфный серверный тест,
и McGregor PACT - Параллельная архитектура для тестирования компонентов.
Ответ 2
Вы можете использовать атрибуты [RowTest] в MBUnit для этого. В приведенном ниже примере показано, где вы передаете методу строку, указывающую, какой класс реализации интерфейса вы хотите создать, а затем создает этот класс через отражение:
[RowTest]
[Row("Class1")]
[Row("Class2")]
[Row("Class3")]
public void TestMethod(string type)
{
IMyInterface foo = Activator.CreateInstance(Type.GetType(type)) as IMyInterface;
//Do tests on foo:
}
В атрибутах [Row] вы можете передать любое произвольное количество входных параметров, например, входные значения для тестирования или ожидаемые значения, возвращаемые вызовами метода. Вам нужно будет добавить аргументы соответствующих типов в качестве входных аргументов метода тестирования.
Ответ 3
Развернувшись на ответе Джо, вы можете использовать атрибут [TestCaseSource] в NUnit аналогично MBUnit RowTest. Вы можете создать источник тестового примера с вашими именами классов. Затем вы можете украсить каждый тест, который имеет атрибут TestCaseSource. Затем, используя Activator.CreateInstance, вы можете применить к интерфейсу и установить его.
Что-то вроде этого (записка - скомпилированная голова)
string[] MyClassNameList = { "Class1", "Class2" };
[TestCaseSource(MyClassNameList)]
public void Test1(string className)
{
var instance = Activator.CreateInstance(Type.FromName(className)) as IMyInterface;
...
}
Ответ 4
Это моя конкретная реализация, основанная на ответе avandeursen:
[TestClass]
public abstract class IMyInterfaceTests
{
protected abstract IMyInterface CreateInstance();
[TestMethod]
public void SomeTest()
{
IMyInterface instance = CreateInstance();
// Run the test
}
}
Каждая реализация интерфейса затем определяет следующий тестовый класс:
[TestClass]
public class MyImplementationTests : IMyInterfaceTests
{
protected override IMyInterface CreateInstance()
{
return new MyImplementation();
}
}
SomeTest
выполняется один раз для каждого конкретного TestClass
, полученного из IMyInterfaceTests
. Используя абстрактный базовый класс, я избегаю необходимости в каких-либо макетных реализациях. Не забудьте добавить TestClassAttribute
в оба класса, иначе это не сработает. Наконец, при желании вы можете добавить к дочернему классу любые специфичные для реализации тесты (например, конструкторы).