LINUX.ORG.RU

Как mock'ать?

 , ,


0

2

У меня есть класс-наследник com.rabbitmq.client.DefaultConsumer из библиотеки RabbitMQ. Надо протестировать этот класс. Я использую spock для тестов. Создаю mock класса com.rabbitmq.client.Channel, хочу заменить вызов Channel.basicPublish на заглушку (чтобы вызывался метод Consumer.handleDelivery, который я перегрузил):

class MessagesSpec extends Specification {

    def "Sending messages"() {
        given:
        def consumeTag = "consumer"
        def exchange = "foo.bar"
        def routingKey = "baz"
        def message = "message"
        def basicProperties = new BasicProperties()
        def envelope = new Envelope(1, false, exchange, routingKey)

        def channel = Mock(Channel)
        AMQMessageConsumer<Message<String>> consumer = new AMQMessageConsumer<Message<String>>(String.class, channel) // Это тестируемый объект
        channel.basicPublish(_ as String, _ as String, _ as BasicProperties, _ as byte[]) >> {
            String e, String rk, BasicProperties bp, byte[] m ->
                consumer.handleDelivery(consumeTag, envelope, bp, m)
        } // Тут заглушка для basicPublish, должен вызываться реальный метод тестируемого класса

        when:
        channel.basicPublish(exchange, routingKey, basicProperties, message.getBytes()) // Вызываю заглушку

        then:
        1 * consumer.handleDelivery(*_)
    }
}

Однако в результате запуск consumer.handleDelivery не происходит:

Too few invocations for:

1 * consumer.handleDelivery(*_)   (0 invocations)

Unmatched invocations (ordered by similarity):

None

Как правильно mock'нуть всё это дело, а то я что-то туплю и не могу сообразить


Мы хотим перейти на spock с EasyMock.

Я проверю на работе. Правда, у нас ApacheMQ.

Если у кого-то из форумчан будут подсказки - буду премного благодарен.

Bioreactor ★★★★★ ()

Честно хотел помочь, но не смог разобраться. Если в кратце, то вы делаете что-то не так :)

Если вы хотите проверить, что channel.basicPublish() вызывает consumer.handleDelivery(), то зачем вы сделали channel моком, а consumer реальным классом? Разве не должно быть наоборот? Вы передаете мок в реальный класс, вызываете метод и затем можете узнать у мока был он вызван или нет.

php-coder ★★★★★ ()
Последнее исправление: php-coder (всего исправлений: 1)
Ответ на: комментарий от php-coder

Это spock.

Меня заинтересовало, поскольку удобно использовать http://meetspock.appspot.com/ и мы хотим переползти на spock.

Вы правы, как я понял, в приведенном коде действительно передается мок в реальный класс.

Bioreactor ★★★★★ ()
Ответ на: комментарий от Bioreactor

Вы правы, как я понял, в приведенном коде действительно передается мок в реальный класс.

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

php-coder ★★★★★ ()

А, кстати, Вы не пробовали спросить у самой команды Спока, там, по-моему можно разместить свой код и разработчики оказывают поддержку

http://meetspock.appspot.com/scripts

Bioreactor ★★★★★ ()
Ответ на: комментарий от php-coder

Моки для меня в новинку, возможно я что-то неправильно понял...

В общем, мне нужно протестировать мой класс (AMQMessageConsumer). Протестить надо то, как принятое сообщение обрабатывается методом handleDelivery. Но для начала хочется, чтобы этот метод вызывался нужное количество раз, чего я собственно и пытаюсь сделать тут.

В реальных ситуациях надо создать канал (channel), через который будет идти вся работа rabbit: объявление новых exchange'ей, объявление очередей, связь exchange с queue, отправка сообщений (channel.basicPublish), добавление класса-слушателя на события очереди сообщений (channel.basicConsume).

Как я понял, мок позволяет проэмулировать работу канала: когда отправляется сообщение через channel.basicPublish, я хочу сразу вызывать callback-метод (это который handleDelivery) в моём созданном классе, вместо того, чтобы реально вешать слушателя и отправлять сообщения. Т.е. выходит так, как будто я подписался на нужный канал и мне приходит сообщение из него, вызывая callback. То, что в тестируемый объект передаётся мок (new AMQMessageConsumer<>(String.class, channel))- это ничего страшного, можно и null передать. Насколько я понял, мне надо создать mock-обёртку именно для канала (его я не тестирую), и метод отправки сообщения в нём (channel.basicPublish) надо пропатчить так, чтобы при его вызове запускался метод моего тестируемого объекта (handleDelivery), который является полноценным объектом.

Правильно ли я понял общий принцип?

memnek ()
Ответ на: комментарий от memnek

Короче, чтобы вам все объяснить нужно написать много текста. У меня нет на это времени и желания. Могу объяснить вам свое видение по скайпу (slava.semushin)

Либо вот тезисы:

  • неправильно замокан basicPublish() — хотели его реализовать, а в итоге вы возвращаете из него замыкание
  • вы зря мокаете channel — получается так, что вы проверяете в тесте свою реализацию этого channel. Вы можете получить ситуацию, когда в реальности код не работает, а тест работает
  • весьма вероятно, что ваша задача выходит за рамки юнит-тестирования и должна решаться с помощью интеграционных тестов
php-coder ★★★★★ ()
Последнее исправление: php-coder (всего исправлений: 1)

Помогите, пожалуйста, нубу

Я упростил Ваш пример.

public class A {
  def m() {
   println "A"
  }
}
public class C {
  def m() {
    println "C"
  }
}
class TestMe extends spock.lang.Specification {

  def "Delegate me"() {

  given:
    A a = new A()
    def c = Mock(C)
    c.m() >> { a.m() }

  when:
    c.m()

  then:
    1 * c.m()
    1 * a.m()
  }
}

Получается

JUnit 4 Runner, Tests: 1, Failures: 1, Time: 141
Test Failure: Delegate me(TestMe)
Too few invocations for:

1 * a.m() (0 invocations)

Unmatched invocations (ordered by similarity):

None


at org.spockframework.mock.runtime.InteractionScope.verifyInteractions(InteractionScope.java:78)
at org.spockframework.mock.runtime.MockController.leaveScope(MockController.java:76)
at TestMe.Delegate me(TestMe.groovy:11)

Т.е.

проходит c.m()

и не проходит a.m()

Я только начал изучать spock, мне понравилось, но многие вещи непонятны, даже после работы с EasyMock.

Читал вот эту статью, но она очень краткая

http://habrahabr.ru/post/137561/

Bioreactor ★★★★★ ()
Последнее исправление: Bioreactor (всего исправлений: 1)
Ответ на: Помогите, пожалуйста, нубу от Bioreactor

А чем помочь-то? В вашем упрощенном примере те же самые ошибки + еще одна (вы зачем-то проверяете, что с.m() был вызван).

php-coder ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.