Статьи

Спок прошел следующий тест — безболезненное

В  последнем посте  я говорил о нашей потребности в некоторых улучшенных инструментах тестирования, о нашем выборе  Spock  как о чем-то, что всплывает, и о том, как насмешливо выглядит в Spock.

Поскольку этот блог стал довольно длинным, я сохранил следующий выпуск для отдельной записи.

Сегодня я хочу посмотреть на заглушки.

Stubbing
Mocking отлично подходит для проверки  выходных данных  — в примере из предыдущего поста мы проверяем, что процесс кодирования массива вызывает правильные вещи при выходе, если вам нравится — что правильные вещи попадают в  bsonWriter.

Stubbing отлично подходит для фальсификации ваших  мнений  (я не знаю, почему эта разница никогда не случалась со мной раньше, но  разговор Колина на Devoxx UK сделал это действительно понятным для меня). 

Одна из вещей, которую нам нужно сделать на уровне совместимости нового драйвера, — обернуть все исключения нового стиля, которые могут быть выброшены новым слоем архитектуры, и превратить их в исключения старого стиля для целей обратной совместимости. Иногда тестировать исключительные случаи … сложно. Поэтому я решил сделать это со Споком.

class DBCollectionSpecification extends Specification {
    private final Mongo mongo = Mock()
    private final ServerSelectingSession session = Mock()
 
    private final DB database = new DB(mongo, 'myDatabase', new DocumentCodec())
    
    @Subject
    private final DBCollection collection = new DBCollection('collectionName', database, new DocumentCodec())
 
    def setup() {
        mongo.getSession() >> { session }
    }
 
    def 'should throw com.mongodb.MongoException if rename fails'() {
        setup:
        session.execute(_) >> { throw new org.mongodb.MongoException('The error from the new Java layer') }
 
        when:
        collection.rename('newCollectionName');
 
        then:
        thrown(com.mongodb.MongoException)
    }
}

Так что здесь мы можем использовать реальный  DB класс, но с насмешкой  Mongo , которая вернет нам «насмешку»  Session. Хотя на самом деле это не насмешка, это скорее заглушка, потому что мы хотим рассказать ей, как вести себя, когда она  вызывается — в этом тесте мы хотим заставить ее генерировать  org.mongodb.MongoException всякий раз, когда  executeвызывается. Для нас не имеет значения, что передается в метод execute (вот что означает подчеркивание в строке 16), важно то, что при вызове он выдает правильный тип Exception.

Как и прежде,  whenраздел: показывает бит, который мы на самом деле пытаемся проверить. В этом случае мы хотим позвонить rename.

Тогда, наконец, then: В разделе утверждается, что мы получили правильный вид исключения. Это не очень ясно, хотя я и оставил полное пространство имен, чтобы попытаться уточнить, но цель в том, чтобы все, org.mongodb.MongoException что выбрасывалось новой архитектурой, превращалось в соответствующее  com.mongodb.MongoException. Нам как-то «повезло», потому что старый код находится в неправильной структуре пакета, а в новой архитектуре у нас есть шанс исправить это и поместить вещи в нужное место.

После того, как я отследил все места, в которых могут возникать исключения, и начал писать такого рода тесты для реализации этих путей кода, я не только почувствовал себя более уверенно, что мы не нарушим обратную совместимость, пропуская неправильные исключения, но мы также нашли наше тестовое покрытие пошли вверх — и что более важно, в  ООНсчастливые пути, которые зачастую сложнее проверить.

Я упомянул в последнем посте, что мы уже выполнили несколько простых операций, чтобы помочь нам протестировать драйвер данных. Почему бы просто не продолжать использовать этот подход? 

Ну, эти заглушки в итоге выглядят так:

private static class TestAsyncConnectionFactory implements AsyncConnectionFactory {
    @Override
    public AsyncConnection create(final ServerAddress serverAddress) {
        return new AsyncConnection() {
            @Override
            public void sendMessage(final List<ByteBuf> byteBuffers, final SingleResultCallback<Void> callback) {
                throw new UnsupportedOperationException();
            }
 
            @Override
            public void receiveMessage(final ResponseSettings responseSettings, final SingleResultCallback<ResponseBuffers> callback) {
                throw new UnsupportedOperationException();
            }
 
            @Override
            public void close() {
            }
 
            @Override
            public boolean isClosed() {
                throw new UnsupportedOperationException();
            }
 
            @Override
            public ServerAddress getServerAddress() {
                throw new UnsupportedOperationException();
            }
        };
    }
}

Ик.

И в итоге вы расширяете их, так что вы можете просто переопределить интересующий вас метод (особенно в случае принудительного вызова метода для исключения). Больше всего меня раздражают, что эти заглушки живут вне реальных тестов, поэтому вы не можете легко увидеть, каково ожидаемое поведение. В тесте Спока ожидаемое поведение заглушки определяется в строке 16, вызов, который его провоцирует, находится в строке 19, а код, который проверяет ожидание, — в строке 22. Все это находится даже в окне самого маленького монитора.

Так что загрызть в Споке безболезненно. Следующий!