Пересмешные Clojure протоколы - PullRequest
5 голосов
/ 09 апреля 2011

Можно ли использовать одну из популярных фреймворков для Java-моделирования, таких как EasyMock или Mockito , для имитации протоколов Clojure, определенных с defprotocol? Если да, то как?

Ответы [ 2 ]

9 голосов
/ 10 апреля 2011

Вы должны иметь возможность макетировать протоколы, используя любую фиктивную библиотеку. Под прикрытием каждый протокол использует Java-интерфейс в качестве детали реализации, и вы можете просто посмеяться над этим интерфейсом.

Тем не менее, не делай этого! Пересмешивать в Java нелепо сложно из-за отражения, уровней защиты, конечных классов и т. Д. Каждый раз, когда вам нужен объект Clojure, который реализует протокол, просто вызовите reify , например,

 (defprotocol Foo (method-a [_]) (method-b [_]))
 -> Foo

 (let [stub (reify Foo (method-a [_] :stubbed))] 
   (method-a stub))
 -> :stubbed

Обратите внимание, что вам не нужно заглушать методы, которые вы не планируете вызывать.

2 голосов
/ 04 июня 2015

Похоже, что более поздние версии Midje предоставляют эту функциональность довольно хорошо.

Во-первых, я хотел бы отметить, что этот вид насмешек очень полезен при разбиении больших программ на компоненты (например,собран с помощью внедрения зависимостей с библиотеками, такими как библиотека компонентов Stuart Sierra ).Если у меня есть какой-то компонент, который изолирует набор функций с побочными эффектами в концептуальном компоненте, я, безусловно, хочу испытательный фреймворк, который позволит мне внедрить отдельный компонент, чтобы я мог:

  1. Напишите код, который использует компонент еще до его создания (сверху вниз).
  2. Тестовые функции, которые используют этот компонент в отрыве от реальной реализации компонента.

Вы можете использоватьMockito или какая-то другая библиотека, но я согласен, что такое решение не будет особенно элегантным.

К сожалению, протоколы и записи генерируют классы, которые Midje не может взломать так же легко, как функции ... так что вам придетсяслегка измените свой код:

(defrecord-openly SideEffectThing [connection]
 ISideEffect
 (persist [this thing] :unfinished)
 (read [this] :unfinished)
)

См. Документацию Midje по рабочему режиму , чтобы узнать, как сделать это изменение не влияющим на вашу рабочую среду выполнения.

Путем определения вашего компонентас defrecord-openly вы получаете возможность указать поведение методов компонента uСпойте «предоставленный» механизм Мидье:

(fact "you can test in terms of a record's methods"
  (let [obj (->SideEffectThing :fake-connection)]
   (user-of-side-effect-thing obj) => 0
   (provided
    (read obj) => -1)
  )
 )

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

Для завершения я сравню эквивалентный код Java Mockito с приведенным выше решением.В Java:

interface ISideEffect { int read(); void write(Object something); }
class SideEffectThing implements ISideEffect { ... }

// in test sources:
public class SomeOtherComponentSpec {
   private ISideEffect obj;
   @Before
   public void setup() { obj = mock(ISideEffect.class); }
   @Test
   public void does_something_useful() {
      when(obj.read()).thenReturn(-1);
      OtherComponent comp = new OtherComponent(obj);

      int actual = comp.doSomethingUseful();

      assertEquals(0, actual);
      verify(obj).read();
   }

это решение Java моделирует компонент, определяет требуемое поведение этого компонента, а затем проверяет не только то, что сам компонент работает должным образом, но также и то, что компонент зависит от вызова read() каким-то образом.Mockito также поддерживает сопоставление с образцом для аргументов (и захвата), чтобы проанализировать, как компонент использовался, если необходимо.

Приведенный выше пример Midje делает большую часть этого, и в гораздо более ясной форме.Если вы укажете (в предоставленном предложении), что функция вызывается с конкретными аргументами, тогда тест не пройдёт, если это не так.Если вы укажете, что функция вызывается более одного раза (а это не так), то это ошибка.Например, чтобы указать, что чтение будет вызываться 3 раза и должно возвращать разные значения:

(fact "you can test in terms of a record's methods"
  (let [obj (->SideEffectThing :fake-connection)]
   (user-of-side-effect-thing obj) => 0
   (provided
    (read obj) => -1
    (read obj) => 6
    (read obj) => 99
    )
  )
 )

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

...