Мы постоянно боремся за разработку нового Java-драйвера MongoDB между использованием инструментов, которые сделают нас тяжелыми, и минимизацией зависимостей, которые пользователь должен загрузить, чтобы использовать наш драйвер. В идеале мы хотим, чтобы количество зависимостей было равно нулю.
Однако это не относится к тестированию. По крайней мере, мы собираемся использовать JUnit или TestNG (мы использовали testng в предыдущей версии, мы перешли на JUnit для 3.0). До недавнего времени мы усердно работали над устранением необходимости в фальшивом фреймворке — драйвер не является крупным приложением с взаимодействующими сервисами, большинство вещей можно протестировать либо как интеграционный тест, либо с помощью очень простых заглушек.
Недавно я работал над уровнем сериализации — мы вносим довольно большие изменения в модель кодирования и декодирования между
BSON и Java, мы надеемся, что это упростит нашу жизнь, но также облегчит работу ODM (Object- Document Mappers) и сторонние библиотеки. На этом уровне имеет смысл вводить макеты — я хочу убедиться, что определенные методы вызываются на записывающем устройстве, например, я не хочу проверять фактические байтовые значения, это не очень полезно для документации ( хотя есть уровень, на котором это разумно).
Мы начали использовать
JMock для начала, это то, что я использовал некоторое время, и это дало нам то, что мы хотели — простую среду для насмешек (я попробовал
Mockitoтоже, но я не очень привык к сообщениям о сбоях, поэтому мне было очень сложно выяснить, что не так, когда тест не пройден).
Я знал от своих шпионов в LMAX, что есть какая-то
среда тестирования Groovy под названием
Spock, которая, по-видимому, великолепна, но я сразу же от нее отказался — я очень сильно чувствую, что тесты — это документация, и поскольку пользователи драйвера Java в основном являются разработчиками Java, я чувствовалось, что введение тестов на другом языке было дополнительной сложностью, в которой мы не нуждались.
Потом я пошел в GeeCON, и мой бывший коллега из
Израиля заставил меня пойти
на разговор о Споке, И я понял, насколько я был не прав. Далеко не добавляя сложности, здесь был прекрасный, описательный способ написания тестов. Он гибкий, но в то же время достаточно структурированный, чтобы вы могли мыслить так, чтобы создавать хорошие тесты.
Поскольку мы уже используем
gradle , который также является Groovy, мы решили, что стоит посмотреть, принесет ли Спок какие-то преимущества.
Во время всплеска я преобразовал выбор наших тестов в тесты Спока, чтобы посмотреть, как это выглядит на
реальной базе кода. У меня были очень конкретные вещи, которые я хотел попробовать:
- осмеяние
- Stubbing
- Тестирование на основе данных
В разговоре я также увидел полезную аннотацию вроде
@Requires
, которую я почти уверен, что мы собираемся использовать, но я не думаю, что она превратилась в сборку.
Итак, получите это, я собираюсь написать сообщение в блоге с Actual Code. Да, я знаю, вы все думали, что я был просто евангелистом пони в эти дни и больше не делал никакого реального кодирования.
Прежде всего, Mocking
Итак, как я уже сказал, у меня есть ряд тестов, проверяющих, что кодирование объектов Java работает так, как мы ожидаем. Самый простой способ проверить это — посмеяться над нашим
классом BSONWriter, чтобы убедиться, что с ним происходят правильные взаимодействия. Это хороший способ проверить, что когда вы предоставляете кодировщику определенный набор данных, он сериализуется так, как ожидает BSON. Эти тесты в итоге выглядели примерно так:
@Test
public void shouldEncodeListOfStrings() {
final List<String> stringList = asList("Uno", "Dos", "Tres");
context.checking(new Expectations() {{
oneOf(bsonWriter).writeStartArray();
oneOf(bsonWriter).writeString("Uno");
oneOf(bsonWriter).writeString("Dos");
oneOf(bsonWriter).writeString("Tres");
oneOf(bsonWriter).writeEndArray();
}});
iterableCodec.encode(bsonWriter, stringList);
}
(Да, я все еще учу испанский).
Так что это неплохо, мои тесты проверяют, что с учетом списка строк они корректно сериализуются. Что не очень хорошо, так это некоторые накладные расходы на установку:
public class IterableCodecTest {
//CHECKSTYLE:OFF
@Rule
public final JUnitRuleMockery context = new JUnitRuleMockery();
//CHECKSTYLE:ON
// Mocks
private BSONWriter bsonWriter;
// Under test
private final IterableCodec iterableCodec = new IterableCodec(Codecs.createDefault());
@Before
public void setUp() {
context.setImposteriser(ClassImposteriser.INSTANCE);
context.setThreadingPolicy(new Synchroniser());
bsonWriter = context.mock(BSONWriter.class);
}
@Test
public void shouldEncodeListOfStrings() {
final List<String> stringList = asList("Uno", "Dos", "Tres");
context.checking(new Expectations() {{
oneOf(bsonWriter).writeStartArray();
oneOf(bsonWriter).writeString("Uno");
oneOf(bsonWriter).writeString("Dos");
oneOf(bsonWriter).writeString("Tres");
oneOf(bsonWriter).writeEndArray();
}});
iterableCodec.encode(bsonWriter, stringList);
}
}
Очевидно, что некоторые вещи будут звонить в тревожные звонки некоторых людей, но давайте на минуту предположим, что все решения были приняты осторожно и что соответственно были взвешены все за и против.
Так:
- Дразнить конкретные классы в JMock — это не красиво, просто посмотрите на этот
setUp
метод. - Мы используем метод
JUnitRuleMockery
, который представляется наилучшей практикой (и означает, что вы с меньшей вероятностью забудете@RunWith(JMock.class)
аннотацию), но checkstyle ненавидит его — публичные поля плохие, как мы все знаем.
Но это нормально, небольшое количество шаблонов для всех наших тестов, которые включают насмешку, — это нормальная цена, чтобы заплатить за хорошие тесты.
Я преобразовал этот тест в тест Спока. Пуристы Groovy заметят, что это все еще очень Java-у, и это намеренно — я хочу, чтобы эти тесты, по крайней мере, на этом этапе, пока мы к этому привыкли, были знакомы программистам на Java, нашей основной аудитории.
class IterableCodecSpecification extends Specification { private BSONWriter bsonWriter = Mock(); @Subject private final IterableCodec iterableCodec = new IterableCodec(Codecs.createDefault()); public void 'should encode list of strings'() { setup: List<String> stringList = ['Uno', 'Dos', 'Tres']; when: iterableCodec.encode(bsonWriter, stringList); then: 1 * bsonWriter.writeStartArray(); 1 * bsonWriter.writeString('Uno'); 1 * bsonWriter.writeString('Dos'); 1 * bsonWriter.writeString('Tres'); 1 * bsonWriter.writeEndArray(); } }
Некоторые начальные наблюдения:
- Это действительно простая вещь, но мне нравится иметь
@Subject
аннотацию к тестируемой вещи. Теоретически должно быть очевидно, какое из ваших полей или переменных является объектом тестирования, но на практике это не всегда так. - Хотя меня это бесит, когда я занимался Java последние 15 лет, мне действительно нравится String для имени метода — хотя в этом случае он аналогичен эквиваленту JMock / JUnit, он дает гораздо большую гибкость для описания цели этого теста.
- Пересмешивание безболезненно, с простым вызовом
Mock()
, хотя мы все еще высмеиваем конкретные классы (это делается просто путем добавления cglib и obgenesis к зависимостям). - Мне нравится, что фазы Spock (
setup: when: then:
) документируют различные части теста, а также являются полезными магическими ключевыми словами, которые говорят Споку, как запустить тест. Я знаю, что другие фреймворки обеспечивают это, но мы работали с JUnit, и у меня была привычка комментировать мои шаги//given //when //then
. - Благодаря Groovy создание списков занимает меньше времени (строка 9). Не имеет большого значения, но просто облегчает чтение.
- Я очень привык к тому, как ожидания устанавливаются в JMock, но я должен сказать, что
1 * bsonWriter.blahblahblah()
это гораздо более читабельно. - Мне нравится, что все, что после,
then:
является утверждением, я думаю, оно действительно ясно дает понять, что вы ожидаете после того, как вызовете то, что тестируете.
Так что насмешка это круто. Что дальше?