Единичные тесты Архитектура Вопрос
Итак, я начал выполнять модульные тесты для следующего кода:
public interface MyInterface {
void MyInterfaceMethod1();
void MyInterfaceMethod2();
}
public class MyImplementation1 implements MyInterface {
void MyInterfaceMethod1() {
// do something
}
void MyInterfaceMethod2() {
// do something else
}
void SubRoutineP() {
// other functionality specific to this implementation
}
}
public class MyImplementation2 implements MyInterface {
void MyInterfaceMethod1() {
// do a 3rd thing
}
void MyInterfaceMethod2() {
// do something completely different
}
void SubRoutineQ() {
// other functionality specific to this implementation
}
}
с несколькими реализациями и ожиданием еще большего.
Моя первоначальная мысль заключалась в том, чтобы сэкономить время на повторную запись модульных тестов примерно так:
public abstract class MyInterfaceTester {
protected MyInterface m_object;
@Setup
public void setUp() {
m_object = getTestedImplementation();
}
public abstract MyInterface getTestedImplementation();
@Test
public void testMyInterfaceMethod1() {
// use m_object to run tests
}
@Test
public void testMyInterfaceMethod2() {
// use m_object to run tests
}
}
который я мог бы затем легко подклассифицировать, чтобы протестировать дополнительные дополнительные методы реализации следующим образом:
public class MyImplementation1Tester extends MyInterfaceTester {
public MyInterface getTestedImplementation() {
return new MyImplementation1();
}
@Test
public void testSubRoutineP() {
// use m_object to run tests
}
}
а также для имплантации 2.
Итак, мой вопрос на самом деле: есть ли причина не делать этого? JUnit, похоже, очень нравится, и он удовлетворяет мои потребности, но на самом деле я не видел ничего подобного в каких-либо модульных тестовых книгах и примерах, которые я читал.
Есть ли какая-то лучшая практика, которую я невольно нарушаю? Устраиваю ли я себя в сердце? Есть ли просто лучший способ, который я не рассматривал?
Спасибо за любую помощь.
Ответы
Ответ 1
Есть ли какая-то причина не делать этого?
Нет. Сделай это. Тесты являются классами именно по этой причине.
Я не видел ничего подобного в любой из книг тестирования модулей и примеров, которые я читал.
Продолжайте читать. Введения не распространяются на это.
Есть ли какая-то лучшая практика, которую я невольно нарушаю?
Нет.
Я настраиваю себя на страдание по дороге?
Нет.
Некоторые люди нервничают по поводу "хрупких тестов". Вы можете найти здесь несколько вопросов, ищущих способы сделать так, чтобы изменения в программном обеспечении также не приводили к изменениям в тестах. В конечном итоге попытки создания "надежных" тестов глупы. Вы хотите, чтобы тесты были написаны так, что каждое небольшое изменение на видимом уровне интерфейса программного обеспечения требует перезаписи кода.
Вы хотите тесты, чтобы невидимые внутренние изменения не требовали перезаписи кода.
Использование классов и подклассов ортогонально этим соображениям.
Есть ли намного лучший способ, который я не рассматривал?
Нет. Объектная ориентация - это точка. Тесты - это класс именно по этой причине.
Ответ 2
Пока я поддерживаю SLott 100%, я бы также рассмотрел параметризированные тесты JUnit вместо иерархии тестовых классов для этого:
@RunWith(Parameterized.class)
public class MyInterfaceTester {
private MyInterface m_object;
public void MyInterfaceTester(MyInterface object) {
m_object = object;
}
@Parameters
public static Collection<Object[]> data() {
List<Object[]> list = new ArrayList<Object[]>();
list.add(new Object[]{new MyImplementation1()});
list.add(new Object[]{new MyImplementation2()});
return list;
}
@Test
public void testMyInterfaceMethod1() {
// use m_object to run tests
}
@Test
public void testMyInterfaceMethod2() {
// use m_object to run tests
}
}
Нет необходимости в иерархии классов тестов: просто добавьте новую реализацию, добавив еще один элемент списка в метод data
.
Ответ 3
Если вы действительно делаете одну и ту же настройку и сбиваете в каждом тестовом классе, то то, что вы делаете, прекрасно, но я нахожу, что на практике это почти никогда не бывает. Фактически, большую часть времени, когда у вас есть способ установки, который создает тестовые данные, как вы здесь, даже не то, что вы хотите. Вместо этого в тестовом классе вы настраиваете любую инфраструктуру, и каждый тестовый метод устанавливает свой собственный экземпляр объекта для проверки его аспект.