Когда я начинаю повторять себя в модульных тестах, создавая одни и те же объекты и подготавливая данные для запуска теста, я разочарован в своем дизайне. Длинные методы тестирования с большим количеством дублирования кода просто не выглядят правильно. Чтобы упростить и укоротить их, есть по существу два варианта, по крайней мере в Java: 1) частные свойства, инициализированные через @Before и @BeforeClass, и 2) частные статические методы. Они оба выглядят для меня анти-ООП, и я думаю, что есть альтернатива. Позвольте мне объяснить.
public final class MetricsTest {
private File temp;
private Folder folder;
@Before
public void prepare() {
this.temp = Files.createTempDirectory("test");
this.folder = new DiscFolder(this.temp);
this.folder.save("first.txt", "Hello, world!");
this.folder.save("second.txt", "Goodbye!");
}
@After
public void clean() {
FileUtils.deleteDirectory(this.temp);
}
@Test
public void calculatesTotalSize() {
assertEquals(22, new Metrics(this.folder).size());
}
@Test
public void countsWordsInFiles() {
assertEquals(4, new Metrics(this.folder).wc());
}
}
Я думаю, что очевидно, что делает этот тест. Во-первых, в prepare () он создает «тестовое оснащение» типа Folder. Это используется во всех трех тестах в качестве аргумента для конструктора Metrics. Настоящий класс, тестируемый здесь, - это Metrics, а this.folder - это то, что нам нужно для его тестирования.
Что не так с этим тестом? Существует одна серьезная проблема: связь между методами тестирования. Методы испытаний (и все тесты в целом) должны быть совершенно изолированы друг от друга. Это означает, что смена одного теста не должна влиять на другие. В этом примере это не так. Когда я хочу изменить тест countsWords (), я должен изменить внутренности before (), что повлияет на другой метод в тестовом классе.
При всем уважении к JUnit идея создания тестовых светильников в @Before и @After ошибочна, в основном потому, что она побуждает разработчиков к соединению методов тестирования.
Вот как мы можем улучшить наш тест и изолировать методы тестирования:
Что не так с этим тестом? Существует одна серьезная проблема: связь между методами тестирования. Методы испытаний (и все тесты в целом) должны быть совершенно изолированы друг от друга. Это означает, что смена одного теста не должна влиять на другие. В этом примере это не так. Когда я хочу изменить тест countsWords (), я должен изменить внутренности before (), что повлияет на другой метод в тестовом классе.
При всем уважении к JUnit идея создания тестовых светильников в @Before и @After ошибочна, в основном потому, что она побуждает разработчиков к соединению методов тестирования.
Вот как мы можем улучшить наш тест и изолировать методы тестирования:
public final class MetricsTest {
@Test
public void calculatesTotalSize() {
final File dir = Files.createTempDirectory("test-1");
final Folder folder = MetricsTest.folder(
dir,
"first.txt:Hello, world!",
"second.txt:Goodbye!"
);
try {
assertEquals(22, new Metrics(folder).size());
} finally {
FileUtils.deleteDirectory(dir);
}
}
@Test
public void countsWordsInFiles() {
final File dir = Files.createTempDirectory("test-2");
final Folder folder = MetricsTest.folder(
dir,
"alpha.txt:Three words here",
"beta.txt:two words"
"gamma.txt:one!"
);
try {
assertEquals(6, new Metrics(folder).wc());
} finally {
FileUtils.deleteDirectory(dir);
}
}
private static Folder folder(File dir, String... parts) {
Folder folder = new DiscFolder(dir);
for (final String part : parts) {
final String[] pair = part.split(":", 2);
this.folder.save(pair[0], pair[1]);
}
return folder;
}
}
Это выглядит лучше сейчас? Мы еще не пришли, но теперь наши методы тестирования полностью изолированы. Если я хочу изменить один из них, я не буду затрагивать других, потому что передаю все параметры конфигурации в приватную статическую утилиту (!) Method folder ().
Полезный метод, да? Да, это пахнет.
Главная проблема с этим дизайном, хотя и лучше, чем предыдущий, заключается в том, что он не предотвращает дублирование кода между «классами» тестирования. Если мне понадобится аналогичное тестовое устройство типа Folder в другом тестовом случае, мне придется переместить этот статический метод. Или, что еще хуже, мне придется создать класс служебных программ. Да, в объектно-ориентированном программировании нет ничего хуже классов полезности.
Гораздо лучше было бы использовать «поддельные» объекты вместо частных статических утилит. Вот как. Сначала мы создаем поддельный класс и помещаем его в src / main / java. Этот класс может использоваться в тестах, а также в производственном коде, если это необходимо (Fk для «поддельного»):
Полезный метод, да? Да, это пахнет.
Главная проблема с этим дизайном, хотя и лучше, чем предыдущий, заключается в том, что он не предотвращает дублирование кода между «классами» тестирования. Если мне понадобится аналогичное тестовое устройство типа Folder в другом тестовом случае, мне придется переместить этот статический метод. Или, что еще хуже, мне придется создать класс служебных программ. Да, в объектно-ориентированном программировании нет ничего хуже классов полезности.
Гораздо лучше было бы использовать «поддельные» объекты вместо частных статических утилит. Вот как. Сначала мы создаем поддельный класс и помещаем его в src / main / java. Этот класс может использоваться в тестах, а также в производственном коде, если это необходимо (Fk для «поддельного»):
public final class FkFolder implements Folder, Closeable {
private final File dir;
private final String[] parts;
public FkFolder(String... prts) {
this(Files.createTempDirectory("test-1"), parts);
}
public FkFolder(File file, String... prts) {
this.dir = file;
this.parts = parts;
}
@Override
public Iterable<File> files() {
final Folder folder = new DiscFolder(this.dir);
for (final String part : this.parts) {
final String[] pair = part.split(":", 2);
folder.save(pair[0], pair[1]);
}
return folder.files();
}
@Override
public void close() {
FileUtils.deleteDirectory(this.dir);
}
}
Вот как теперь будет выглядеть наш тест:
public final class MetricsTest {
@Test
public void calculatesTotalSize() {
final String[] parts = {
"first.txt:Hello, world!",
"second.txt:Goodbye!"
};
try (final Folder folder = new FkFolder(parts)) {
assertEquals(22, new Metrics(folder).size());
}
}
@Test
public void countsWordsInFiles() {
final String[] parts = {
"alpha.txt:Three words here",
"beta.txt:two words"
"gamma.txt:one!"
};
try (final Folder folder = new FkFolder(parts)) {
assertEquals(6, new Metrics(folder).wc());
}
}
}
Как вы думаете? Разве это не лучше, чем предлагает JUnit? Разве это не более многоразово и расширяемо, чем утилитарные методы?
Подводя итог, я считаю, что строительные леса в модульном тестировании должны выполняться через поддельные объекты, которые поставляются вместе с кодом производства.
Подводя итог, я считаю, что строительные леса в модульном тестировании должны выполняться через поддельные объекты, которые поставляются вместе с кодом производства.
Комментариев нет:
Отправить комментарий