Статьи

Clojure: еще одна среда тестирования — ожидания

Когда-то я написал « Ожидания для Руби» . Я хотел простую структуру тестирования, которая позволяла бы мне определять мой тест с наименьшим количеством кода.

Теперь, когда я провожу большую часть своего времени в Clojure, я решил создать версию Expectations for Clojure .

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

Так зачем вам это использовать?

Тесты запускаются автоматически, Clojure ненавидит побочные эффекты, да, я вас слышу. Но я ненавижу тратить время и повторять код. В результате Expectations запускает все тесты на отключение JVM. Это позволяет вам выполнить один файл для запуска всех тестов в этом файле, без необходимости указывать что-либо дополнительное. Есть также ловушка, которую вы можете вызвать, если не хотите, чтобы тесты запускались автоматически. (Если вы ищете пример, есть бегун JUnit, который отключает выполнение тестов при выключении).

Что проверять, выводится из вашего «ожидаемого» значения . Тест на равенство, пожалуй, самый распространенный тест. В «Ожиданиях» тест на равенство выглядит следующим образом.

(expect 3 (+ 1 2))

Это достаточно просто, но что если вы хотите сопоставить регулярное выражение со строкой? Следующий пример делает именно это и использует тот же синтаксис.

(expect #"foo" "afoobar")

Другие общие тесты проверяют, генерируется ли исключение, или проверяют тип фактического значения. Следующие фрагменты проверяют эти два условия.

(expect ArithmeticException (/ 12 0))

(expect String "foo")

Тестирование подмножеств фактического значения . Иногда вам нужно точное совпадение, но часто бывают случаи, когда вас интересует только подмножество фактического значения. Например, вы можете проверить все элементы карты, кроме пар времени и идентификатора (предположительно потому, что они динамические). Следующие тесты показывают, как можно проверить, что некоторые пары ключ / значение находятся на карте, элемент находится в наборе или элемент находится в списке.

;; k/v pair in map. matches subset
(expect {:foo 1} (in {:foo 1 :cat 4}))

;; key in set
(expect :foo (in (conj #{:foo :bar} :cat)))

;; val in list
(expect :foo (in (conj [:bar] :foo)))

Двойной / NaN раздражает . (not = Double / NaN Double / NaN); => верно. Я понимаю, концептуально. На практике я не хочу, чтобы мои тесты проваливались, потому что я не могу сравнить две карты, которые имеют Double / NaN в качестве значения для соответствующего ключа. На самом деле, 100% времени я хочу (= Double / NaN Double / NaN); => правда. И, да, я могу переписать тест и использовать Double / isNaN. Я могу. Но я не хочу. Ожидания позволяют мне притворяться (= Double / NaN Double / NaN); => правда. Это может повредить мне в будущем. Я дам Вам знать. На данный момент я предпочитаю писать краткие тесты, которые ведут себя как «ожидаемые».

Попробуйте переписать это и использовать Double / isNaN (это не весело)

(expect
  {:x 1 :a Double/NaN :b {:c Double/NaN :d 2 :e 4 :f {:g 11 :h 12}}}
  {:x 1 :a Double/NaN :b {:c Double/NaN :d 2 :e 4 :f {:g 11 :h 12}}})

Краткое тестирование Java-объектов . Неизбежно, я, кажется, в конечном итоге с несколькими объектами Java. Я мог бы написать несколько разных ожидаемых операторов, но я выбрал синтаксис, который позволяет мне проверять все сразу.

(given (java.util.ArrayList.)
       (expect
        .size 0
        .isEmpty true))

Урезанные стеки . Я уверен, что иногда полезно просматривать классы Clojure и Java. Тем не менее, я считаю, что в большинстве случаев проблема заключается в моем коде. Ожидания уравновешивают многие из распространенных классов Clojure и Java, оставляя гораздо больше сигнала, чем шума. Ниже приведена информация о трассировке стека при запуске примеров сбоев из кодовой базы Expectations.

ailure in (failure_examples.clj:8) : failure.failure-examples
      raw: (expect 1 (one))
  act-msg: exception in actual: (one)
    threw: class java.lang.ArithmeticException-Divide by zero
           failure.failure_examples$two__375 (failure_examples.clj:4)
           failure.failure_examples$one__378 (failure_examples.clj:5)
           failure.failure_examples$G__381__382$fn__387 (failure_examples.clj:8)
           failure.failure_examples$G__381__382 (failure_examples.clj:8)

Каждая строка трассировки стека взята из моего кода, в котором находится проблема.

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

running

   (expect
     {:z 1 :a 9          :b {:c Double/NaN :d 1 :e 2 :f {:g 10 :i 22}}}
     {:x 1 :a Double/NaN :b {:c Double/NaN :d 2 :e 4 :f {:g 11 :h 12}}})

generates

   failure in (failure_examples.clj:110) : failure.failure-examples
         raw: (expect {:z 1, :a 9, :b {:c Double/NaN, :d 1, :e 2, :f {:g 10, :i 22}}} {:x 1, :a Double/NaN, :b {:c Double/NaN, :d 2, :e 4, :f {:g 11, :h 12}}})
      result: {:z 1, :a 9, :b {:c NaN, :d 1, :e 2, :f {:g 10, :i 22}}} are not in {:x 1, :a NaN, :b {:c NaN, :d 2, :e 4, :f {:g 11, :h 12}}}
     exp-msg: 😡 is in actual, but not in expected
              :b {:f {:h is in actual, but not in expected
     act-msg: :z is in expected, but not in actual
              :b {:f {:i is in expected, but not in actual
     message: :b {:e expected 2 but was 4
              :b {:d expected 1 but was 2
              :b {:f {:g expected 10 but was 11
              :a expected 9 but was NaN

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

Например, ошибка говорит вам

:b {:f {:g expected 10 but was 11

С этими данными довольно легко увидеть проблему в

(expect
  {:z 1 :a 9          :b {:c Double/NaN :d 1 :e 2 :f {:g 10 :i 22}}}
  {:x 1 :a Double/NaN :b {:c Double/NaN :d 2 :e 4 :f {:g 11 :h 12}}})

Ожидания также говорят вам, сравнивая два списка:

  • если списки совпадают, но отличаются только по порядку
  • если списки совпадают, но в одном списке есть дубликаты
  • если списки не совпадают, то этот список больше

    интеграции JUnit . Мой проект использует как Java, так и Clojure. Мне нравится запускать свои тесты в IntelliJ, и мне нравится, когда TeamCity запускает мои тесты как часть сборки. Чтобы сделать это, используя Expectations, все, что вам нужно сделать, это создать класс Java, похожий на пример ниже.

    import expectations.junit.ExpectationsTestRunner;
    import org.junit.runner.RunWith;
    
    @RunWith(expectations.junit.ExpectationsTestRunner.class)
    public class FailureTest implements ExpectationsTestRunner.TestSource{
    
        public String testPath() {
            return "/path/to/the/root/folder/holding/your/tests";
        }
    }

    Тестер ожиданий запускает ваши тесты Clojure так же, как и тесты Java, включая зеленые / красные значки состояния и кликабельные ссылки в случае неудачи.

    Почему бы вам не использовать ожидания?

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

    Если вы готовы жить на переднем крае, не стесняйтесь дать ему шанс.

  •  

    С http://blog.jayfields.com/2010/09/clojure-another-testing-framework.html