Статьи

Тестирование вашего кода с помощью Спока

Spock — это среда тестирования и спецификации для приложений Java и Groovy. Спок это:

  • Чрезвычайно выразительный
  • Облегчает заданный / Когда / Тогда синтаксис для ваших тестов
  • совместим с большинством IDE и CI-серверов.

Звучит интересно? Что ж, вы можете начать играть в Spock очень быстро, посетив веб-консоль Spock . Если у вас есть небольшой тест, который вам нравится, вы можете опубликовать его, как я сделал для этого маленького теста Hello World .

HelloWorld в Споке

Во-первых, тесты Спока написаны на Groovy. Это означает, что некоторый код, который у вас есть с Java, исчезнет. Этот тест Hello World служит мягким введением в некоторые функции Spock.

  • Не нужно указывать, что класс является Public, поскольку это по умолчанию.
  • Нет необходимости объявлять firstWord и lastWord как строки
  • Нет необходимости явно вызывать assert, поскольку каждая строка кода в блоке ожидаемого получает это автоматически. Просто убедитесь, что строки в этом блоке являются логическим выражением. Так что в этом случае это просто выражение равенства, которое будет либо истинным, либо ложным.

Так меньше кода котельной плиты, что дальше? Ну, вы знаете эти действительно длинные имена тестов, которые вы получаете с помощью тестов JUnit, и вместо того, чтобы вызывать этот тест helloWorldIntroductionToSpockTest (), который трудно читать, вы можете просто использовать строку с пробелами для названия теста: Hello World введение в Spock тест Это делает вещи намного более читабельными.

В-третьих, если бы я внес небольшое изменение в тест и изменил firstWord на « Hello1 », тест, конечно, провалился. Но когда я получаю ошибку в Споке, я получаю полный контекст проверенного выражения. Я вижу значение firstWord , значение secondWord и значение после конкатенации, что позволяет намного быстрее диагностировать проблемы при сбое тестов.

Спок показывает контекст неудачи

Насмешка и Стаббинг Не плохо для вступления. Давайте теперь посмотрим на больше возможностей.

В JUnit гораздо мощнее насмешки и заглушки ( и различные дополнения ). Но это не только супер мощный в Споке, но и очень краткий, сохраняя ваш тестовый код очень аккуратным и легко читаемым.

Предположим, что мы хотим заглушить класс с именем PaymentCalculator в нашем тесте, точнее метод, Calculate (Product product, Integer count). В версии с заглушкой мы хотим вернуть количество, умноженное на 10, независимо от стоимости продукта. В Споке мы достигаем этого путем:

1
2
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">PaymentCalculator paymentCalculator = Stub(PaymentCalculator)</span> PaymentCalculator paymentCalculator = Stub (PaymentCalculator)</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">paymentCalculator.calculate(_, _) >> {p, c -> c * 10}</span> paymentCalculator.calculate (_, _) >> {p, c -> c * 10}</span>

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

  1. Подчеркивание в среднем для всех значений
  2. На правой стороне мы видим Groovy Closure. А пока, представьте, что это анонимный метод с двумя входами. р для продукта, с для подсчета. Мы не должны их печатать. Это просто больше кода котельной плиты больше нет.
  3. Закрытие всегда возвращает время отсчета 10. Нам не нужно возвращать оператор. Значение последнего выражения всегда возвращается. Опять же, это означает меньше кода котельной плиты. Когда заглушка становится такой простой и аккуратной, это означает, что вы действительно можете сосредоточиться на тесте — круто.

Параметризованные тесты

Лучший способ объяснить это на примере.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">@Unroll</span> @Unroll</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">def "Check that the rugby player #player who has Irish status #isIrish plays for Ireland"(String player, Boolean isIrish) {</span> def "Убедитесь, что игрок в регби #player, имеющий ирландский статус #isIrish, играет за Ирландию" (String player, Boolean isIrish) {</span>
    <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">given:"An instance of Rugby player validator"</span> дано: «Экземпляр валидатора игрока в регби»</span>
    <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">RugbyPlayerValidator rugbyPlayerValidator = new RugbyPlayerValidator()</span> RugbyPlayerValidator rugbyPlayerValidator = new RugbyPlayerValidator ()</span>
 
    <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">expect:</span> ожидать:</span>
    <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">rugbyPlayerValidator.isIrish(player) == isIrish</span> rugbyPlayerValidator.isIrish (player) == isIrish</span>
 
    <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">where:</span> где:</span>
    <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">player ||</span> игрок ||</span> <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">isIrish</span> isIrish</span>
    <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">"Johny Sexton" ||</span> "Джонни Секстон" ||</span> <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">true</span> правда</span>
    <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">"Stuart Hogg" ||</span> "Стюарт Хогг" ||</span> <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">false</span> ложный</span>
    <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">"Conor Murray" ||</span> "Конор Мюррей" ||</span> <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">true</span> правда</span>
    <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">"George North" ||</span> "Джордж Норт" ||</span> <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">false</span> ложный</span>
    <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">"Jack Nowell" ||</span> "Джек Новелл" ||</span> <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">true</span> правда</span>
 
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">}</span> }</span>

В этом параметризованном тесте мы видим следующее:

  1. Тест параметризован, мы это в сигнатуре теста и в блоке where .
  2. Существует один входной параметр player и один выходной параметр — который соответствует ожидаемому значению.
  3. Тест параметризован пять раз. Входные параметры слева, вывод справа. Конечно, можно получить и то и другое, в этом тесте у нас есть только один из них.
  4. Аннотация @Unroll будет означать, что если тест не пройден, будут выведены значения всех параметров. Сообщение заменит данные игрока в #player, а сведения об ирландском статусе будут заменены на #isIrish. Так, например, « Проверяет, что игрок в регби Джек Новелл, имеющий истинный ирландский статус, играет за Ирландию »

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

Все преимущества Groovy

Что еще? Ну, еще одно важное преимущество — все преимущества Groovy. Например, предположим, что вы тестируете API, который возвращает JSON или XML. Groovy отлично подходит для анализа XML и JSON. Предположим, у нас есть API, который возвращает информацию о спортивных игроках в формате XML. Формат варьируется, но незначительно, в зависимости от вида спорта, в котором они играют:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">Joey Carberry</span> Джои Карберри</span>
   <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left"><details></span> <подробно></span>
 <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left"><rugbysummarycategory></span> <Rugbysummarycategory></span>
  <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left"><players></span> <игроков></span>
    <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left"><player>Joey Carberry</player></span> <player> Джои Карберри </ player></span>
    <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left"><player>Teddy Thomas</player></span> <player> Тедди Томас </ player></span>
  <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left"></players></span> </ Проигрыватели></span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left"></rugbysummarycategory></span> </ Rugbysummarycategory></span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left"></details></span> </ Детали></span>
  
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left"><details></span> <подробно></span>
 <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left"><footballsummarycategory></span> <Footballsummarycategory></span>
   <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left"><players></span> <игроков></span>
     <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left"><player>Lionel Messi</player></span> <player> Лионель Месси </ player></span>
     <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left"><player>Cristiano Ronaldo</player></span> <player> Криштиану Роналду </ player></span>
   <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left"></players></span> </ Проигрыватели></span>
 <span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left"></footballsummarycategory></span> </ Footballsummarycategory></span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left"></details></span> </ Детали></span>

Мы хотим просто вызвать этот API, а затем проанализировать игроков независимо от вида спорта. Мы можем разобрать это полиморфно очень просто в Groovy.

1
2
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">def rootNode = new XmlSlurper().parseText(xml)</span> def rootNode = new XmlSlurper (). parseText (xml)</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">List players = rootNode.'*'.Players.Player*.text()</span> Список игроков = rootNode. '*'. Players.Player * .text ()</span>

Некоторые ключевые моменты:

  1. Сила динамической типизации незамедлительна. Выражение может быть динамически вызвано на rootNode. Не нужно многословного, сложного выражения XPath.
  2. «*», Как подстановочный знак. Это будет охватывать как RugbySummaryCategory, так и FootballSummaryCategory.
  3. Player *, означает для всех элементов Player. Так что никакой глупой многословности для цикла здесь не нужно
  4. Выражение text () просто вытягивает значения текста между соответствующими элементами Player. Так почему же теперь есть список всех игроков, которые можно просто сделать: Players.size () == 4 Помните, что нет необходимости утверждать.

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

1
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">players as Set = ["Joey Carberry", "Teddy Thomas", "Lionel Messi", Cristiano Ranaldo"] as Set</span> игроки как Сет = ["Джои Карберри", "Тедди Томас", "Лионель Месси", Криштиану Ранальдо "] как Сет</span>

Это преобразует оба списка в набор, что означает, что проверка порядка исчезла, и это просто сравнение набора. Есть еще масса преимуществ Groovy, которыми мы можем воспользоваться. Но красота в том, что мы на самом деле не должны.
Весь код Java также допустим в классе Groovy . То же самое относится и к Споку. Это означает, что для тех, кто имеет опыт работы с Java, крутой кривой обучения нет. Они могут кодировать чистую Java, а затем получать советы по Groovy из обзоров кода и т. Д.

Мощные аннотации

Спок также имеет ряд мощных аннотаций для ваших тестов. Опять же, мы видим силу Groovy здесь, поскольку мы можем закрыть эти аннотации. Например:

1
2
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">@IgnoreIf({System.getProperty("os.name").contains("windows")})</span> @IgnoreIf ({System.getProperty ( "os.name"). Содержит ( "окно")})</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">def "I'll run anywhere except windows"() {...}</span> def "Я буду бегать где угодно, кроме Windows" () {...}</span>

Или просто сделайте ваш тест неудачным, если они будут выполняться слишком долго

1
2
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">@Timeout(value = 100, unit=TimeUnit.MILLISECONDS)</span> @Timeout (значение = 100, единица измерения = TimeUnit.MILLISECONDS)</span>
<span class="notranslate" onmouseover="_tipon(this)" onmouseout="_tipoff()"><span class="google-src-text" style="direction: ltr; text-align: left">def "I better be quick"() {...}</span> def "Мне лучше быть быстрым" () {...}</span>

Итак, в целом, Spock против ванили JUnit имеет следующие преимущества:

  1. Тестовая структура обязательна. Нет больше случайных утверждений. Утверждения могут быть только в обозначенных частях кода.
  2. Тестовый код гораздо удобнее для чтения.
  3. Намного больше информации о контексте проваленного теста
  4. Может издеваться и заглушки с гораздо меньшим количеством кода
  5. Может использовать кучу возможностей Groovy, чтобы сделать код гораздо менее многословным
  6. Очень мощная тестовая параметризация, которая может быть выполнена очень аккуратно
  7. Ряд мощных аннотаций.

И одним из часто забываемых моментов является то, что ваш проект не должен быть написан на Groovy. Вы можете сохранить все это в Java и использовать статическую типизацию Java для своего производственного кода, а также использовать мощь и скорость Groovy для своего тестового кода.

До следующего раза береги себя.

Опубликовано на Java Code Geeks с разрешения Алекса Стейвли, партнера нашей программы JCG . Смотрите оригинальную статью здесь: Тестирование вашего кода с помощью Спока

Мнения, высказанные участниками Java Code Geeks, являются их собственными.