Статьи

Спок потрясающий! Серьёзно упрощённая насмешка


Мы постоянно боремся за разработку нового 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:является утверждением, я думаю, оно действительно ясно дает понять, что вы ожидаете после того, как вызовете то, что тестируете.

Так что насмешка это круто. Что дальше?