Category: Groovy, Grails and GORM

junit, test-ng, mockito, spock

Spock or not to Spock?

В последнем выпуске подкаста Разбор полётов ведущие спорили насколько оправданно применять тестовый фреймворк Spock для всех Java проектов.
Изначально разговор зашёл про спецификации и программные TCK которые проверяют что имплементация соответствует спецификации.
Тут пожалуй стоит сделать отступление, и отметить что обычно также делают referrence implementation и как минимум ещё одну реализацию, поскольку любой API нужно реализовывать всегда как минимум дважды чтобы увидеть не появляется ли повторяющийся вспомогательный код перед вызовом API метода который следовало бы тоже вынести в API.
Например, в Java SE для такой частой операции как чтение текстового файла в строку приходилось постоянно писать код преобразования InputStream в StringBuilder и у него уже брать toString(). И только в седьмой джаве появился метод Files.readAllBytes() тем не менее по прежнему требующий ещё дополнительной строчки преобразования массива байтов в строку с указанием кодировки.

Ещё один важный момент, что TCK являются для реализации внешними тестами, в то время как у неё могут быть свои собственные, тестирующие именно особенности реализации.
Я это всё расписал чтобы иметь более полную картину.

И тут у ведущих возник естественный вопрос: если у нас уже есть программный TCK, то, по сути, он уже и есть вообщем-то спецификацией, только записанной уже в более формальном языке программы.
Это совершенно правильная мысль. Единственное различие в них в том, что спецификация является текстом описывающим систему для людей, на естественном языке, в то время как от TCK требуется только дать однозначный ответ — соответствует реализация спецификации или нет. И очень вероятна ситуация когда по коду TCK мы вообще не сможем понять почему все работает именно так, поскольку детали опущены в коде TCK но расписаны в самой спецификации.
Вообще тут даже можно утверждать что refference implementation и тесты самой реализации тоже так же могут служить заменой или дополнением спецификации.
Это одна из причин почему программисты так любят open source — ты всегда можешь узнать как точно работает система на которую ты полагаешься.

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

Story: Returns go to stock

In order to keep track of stock
As a store owner
I want to add items back to stock when they're returned

Scenario 1: Refunded items should be returned to stock
Given a customer previously bought a black sweater from me
And I currently have three black sweaters left in stock
When he returns the sweater for a refund
Then I should have four black sweaters in stock

В идеале эти сценарии писались бы не программистами а тестировщиками в месте с продакт овнером. Идея, на сколько я знаю, не очень прижилась из-за того что сценарии всё равно находятся отдельно от кода, что требует дополнительных усилий на синхронизацию.
Поэтому начали искать способы перенести эти сценарии прямо в тестовый код. Но тут же упёрлись в ограниченный синтаксис самих языков программирования непригодный для этой цели.
Проблему пытались обойти аннотациями, но код получался плохо читабельным. Вот например JBehave поверх Java:

private Game game;
private StringRenderer renderer;

@Given("a $width by $height game")
public void theGameIsRunning(int width, int height) {
game = new Game(width, height);
renderer = new StringRenderer();
game.setObserver(renderer);
}

@When("I toggle the cell at ($column, $row)")
public void iToggleTheCellAt(int column, int row) {
game.toggleCellAt(column, row);
}

@Then("the grid should look like $grid")
public void theGridShouldLookLike(String grid) {
assertThat(renderer.asString(), equalTo(grid));
}

Но например для Groovy, динамического языка программирования поверх JVM многих ограничений Java нет. И поверх него был создан прекрасный фреймворк Spock. Вот пример теста\спецификации:

class HelloSpock extends spock.lang.Specification {
def "length of Spock's and his friends' names"() {
expect:
name.size() == length

where:
name | length
"Spock" | 5
"Kirk" | 4
"Scotty" | 6
}
}

Это data driven тест с двумя секциями expect и where. В секции expect мы пишем выражение которое проверяем, а в секции where мы объявили набор данных из трёх имён которые будут последовательно подставляться в это проверочное выражение expect.
Имя тестового метода объявлено в кавычках и может содержать пробелы и записано в человекочитаемом виде.

Он оказался настолько хорош, что один из ведущих, Барух, выдвинул тезис что именно Spock следует применять во всех проектах на Java для тестирования.
Из моего опыта я могу полностью подтвердить что это качественно новый уровень удобства тестирования недоступный другим фреймворкам и я полностью согласен с Барухом.

Но тут возникает несколько опасений.
1. Не все захотят изучать новый язык программирования Groovy для всего лишь тестов.
Я считаю что Groovy, в оличие от например Scala, это просто диалект Java. Почти любой код на Java является валидным кодом на Groovy. Можно прям взять файл и поменять расширение с .java на .groovy и он скомпилится. Поэтому по сути программистам можно даже не выучивать его для написания тестов.
Groovy очень простой и выразительный язык, имеющий знакомый всем синтаксис, довольно прямолинейный и обладающий отличной «вкуриваемостью».
Есть совсем небольшой гайд для тех кто переходит с Джавы и несколько статей на подобии Groovy за 15 минут. Как по мне, самое главное и что стоит выучить поскольку оно моментально сокращает код в разы (а то и в десятки раз!) — это литералы [] для коллекций и [:] для map, работа с коллекциями, null safe navigration и Groovy Truth. Выучив только эти вещи у вас будет Quick Win.

У меня был случай когда я устроился на работу Java девелопером где в описании вакансии было скромно сказано «будет плюсом знание Groovy & Grails». Я даже особо внимание не обратил, подумал что может где-то у них есть проект и может меня доучат и попросят там помочь. На собеседовании были стандартные вопросы по Джаве.
И в первый же рабочий день мне дали задание написать небольшой функционал на Groovy. Уже через час мой код был оттестирован, проревьювлен и закомичен.
Конечно, очень много времени потом ушло на разборки с всплывшими подводными камнями, всякие тонкости и выработки оптимально стиля кодирования. Также мне потребовалось изучить механизмы метапрограммирования для создания всевозможных DSL’ов.
Но, тестовый код — он всегда простой. Обычно тебе всё что нужно это вызвать метод и сверить с ожидаемым результатом. Поэтому если у вас продакшен код написан на стандартной Джаве а тесты на Груви то вы почти наверняка не столкнётесь с никакими проблемами и глубже его изучать не будет нужды.
Груви подключается как обычная dependency к проекту и имеет отличную поддержку в IntelliJ Idea. А для Спока в неё ещё есть плагин который правда непонятно зачем нужен.
По своему опыту могу сказать что мне было намного труднее вникнуть в стримы восьмой Джавы чем выучить Груви. Учитыая количество подводных камней даже JavaScript в разы будет сложнее Груви.
Так что если какой-то из разработчиков не сможет постигнуть премудрости Груви для написания тестов то он просто совсем некомпетентен.

2. Груви динамический язык, и хотя и есть возможность помечать классы как @CompileStatic но это не работает в Spock
Да, это так и потенциально есть вероятность что если, например, вы поменяли в продакшен коде имя метода а тестах забыли то в момент компиляции ошибка останется незамеченной.
Но, это совершенно не критично — вы сразу же увидите ошибку когда запустите тест и он завалится.
Кроме того, если пользоваться автоматическим рефакторингом в Idea то таких ошибок даже не возникнет.
Так что проблема совершенно надуманная.

3. Что такого даёт Спок что ради него стоит заморачиваться?

Спок тебя строго заставляет писать тесты в правильной структуре, например строго выдерживать секции given:, when:, then:. Я очень часто видел в тестах на JUnit когда эти логические секции то и дело смешиваются и программисты ленятся выносить их отдельный тест кейс.
Кроме того, все строки в секции then: автоматически являются assert и в совокупности с Groovy Truth это убирает кучу ненужного кода и читабельность невероятно возрастает.

И тут нужно внимательно остановится на асертах. В Споке появились так называемые Power Asserts — штука настолько потрясающая, что её позже перенесли в сам Groovy а позже и на другие языки программирования, но к сожалению кроме Джавы.
Вместо тысячи бестолковых assertEquals(), assertTrue() и всяких матчеров типа Hamcrest в Груви достаточно просто написать ключевое слово assert и вы получите подробный вывод что и где пошло не так:

assert [[1,2,3,3,3,3,4]].first().unique() == [1,2,3]

// Output:
//
// Assertion failed:
// assert [[1,2,3,3,3,3,4]].first().unique() == [1,2,3]
// | | |
// | | false
// | [1, 2, 3, 4]
// [1, 2, 3, 4]

На практике это просто Вау фича! Причём теперь фича самого Груви, так что даже обычный JUnit тест на Груви будет в тысячу раз приятнее.

Ну и лично моя самая любимая фича, это data driven tests. Спок даёт простой и удобный DSL для быстрого написания набора тестовых наборов. Я уже приводил пример выше, но у Николая Алименкова возникло сомнение насчёт их идеалогической правильности.
Когда один из наборов из where: не проходит то весь тест кейс помечается как failed, даже если остальные проходили.
В результате репортинг становится не очень полезным.

Если вы хотите видеть детальный отчёт какой именно тестовый набор провалился, вы можете пометить тест кейс аннотацией @Unroll.
Но Николай вполне справедливо заметил: хорошо, теперь я буду знать какой конкретно тестовый метод не прошёл, но я не буду знать почему, ведь комментария нету.
Следовательно для того чтобы оставить комментарий всё равно придётся дата дривен тест разбивать на несколько тест кейсов.

Когда я столкнулся с точно такой же проблемой я тоже расстроился и хотел было уже разбивать несколько тест кейсов когда смекнул как это красиво решить.
К аннотации @Unroll можно передать шаблон в который будут вставляться значения из data table. И ничто нам не мешает в дата тейбле сделать ещё одно поле с комментарием.
Например, допустим у нас есть валидатор пароля и тест на него может выглядеть так:

@Unroll
def 'checkPassword(#password) valid=#valid : #comment'() {
given:
PasswordValidator validator = new PasswordValidator()
expect:
validator.validate(password) == valid
where:
password | valid | comment
'pwd' | false | 'too short'
'very long password' | false | 'too long'
'password' | false | 'Not enought strength'
'h!Z7abcd' | true | 'valid password'
}

Чётко, кратко и по делу. Такой же тест на JUnit был бы больше в четыре раза и в десять раз менее читабельнее.
Легкость создания дата дривен тестов тебя прям подталкивает их писать — обычно нужно всего-то лишь добавить одну строчку.

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

Я например писал тесты для Grails Cookies плагина и просто посмотрите сколько кейсов в нём покрыто CookieResponseSpec.groovy.

Кроме этого в Споке есть ещё множество приятностей, например моки которые делают жизнь намного лучше. В нём всё построенно на DSL с которым придётся немного подразобратся, но его немного и он легко запоминается.

Вообщем, подытожу немного перефразировав Баруха: Если вы пишете тесты не на Споке — вы занимаетесь хернёй.

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

[Grails] ConfigObject

A popular question about ConfigSlurper:

Hey guys, I had in a project the following:
String variable = grailsApplication.config.grails.property.name + grailsApplication.config.grails.anotherproperty.name

And was working for almost a year but suddenly it stop working and throwing an error regarding the ConfigObject doesn’t have a plus method, did something similar happened to anyone?
Did someone knows why it was working and suddenly stop working ?

It doesn’t work because one of ‘property.name’ or ‘anotherproperty.name’ is not set.

If some option is not set, then ConfigSlurper return instance of ConfigObject.
That’s a common mistake.
Good news is that empty ConfigObject can be casted to false by the Groovy Truth.
Thus, to avoid this kind of mistakes and get null instead of ConfigObject you can use Elvis operator write something like:

// return empty list if supportedLocales isn't set
grailsApplication.config.supportedLocales ?: []

// return null if defaultLocale isn't set
grailsApplication.config.defaultLocale ?: null

Human readable past date formating in Grails

Sometimes it better to write ‘2 days ago’ instad of ’28 Jan 2015′.
You can find a lot of solution in How to calculate “time ago” in Java?.

Here is a simplest snippet for making it in Grails from my project with i18n support.
Just create new tag `timeAgo` in your project taglib:

    static encodeAsForTags = [ timeAgo: [taglib:'html'] ]

    final static long ONE_SECOND = 1000;
    final static long ONE_MINUTE = ONE_SECOND * 60;
    final static long ONE_HOUR = ONE_MINUTE * 60;
    final static long ONE_DAY = ONE_HOUR * 24;

    /**
     Converts time (in milliseconds) to human-readable format
     "<w> days, <x> hours, <y> minutes ago" or "just now"
     <code>
     System.out.println(millisToLongDHMS(123));
     System.out.println(millisToLongDHMS((5 * ONE_SECOND) + 123));
     System.out.println(millisToLongDHMS(ONE_DAY + ONE_HOUR));
     System.out.println(millisToLongDHMS(ONE_DAY + 2 * ONE_SECOND));
     System.out.println(millisToLongDHMS(ONE_DAY + ONE_HOUR + (2 * ONE_MINUTE)));
     System.out.println(millisToLongDHMS((4 * ONE_DAY) + (3 * ONE_HOUR) + (2 * ONE_MINUTE) + ONE_SECOND));
     System.out.println(millisToLongDHMS(42 * ONE_DAY));
     </code>
     output :
     0 second
     5 seconds
     1 day, 1 hour
     1 day and 2 seconds
     1 day, 1 hour, 2 minutes
     4 days, 3 hours, 2 minutes
     42 days
     */
    private String millisToLongDHMS(long duration) {
        StringBuffer res = new StringBuffer();
        long temp;
        if (duration >= ONE_MINUTE) {
            temp = duration / ONE_DAY;
            if (temp > 0) {
                duration -= temp * ONE_DAY;
                res.append(temp).append(' ').append(temp > 1 ? g.message(code: 'timeAgo.days') : g.message(code: 'timeAgo.day')).append(duration >= ONE_MINUTE ? ', ' : '')
            }
            temp = duration / ONE_HOUR;
            if (temp > 0) {
                duration -= temp * ONE_HOUR;
                res.append(temp).append(' ').append(temp > 1 ? g.message(code: 'timeAgo.hours') : g.message(code: 'timeAgo.hour')).append(duration >= ONE_MINUTE ? ', ' : '')
            }
            temp = duration / ONE_MINUTE
            if (temp > 0) {
                res.append(temp).append(' ').append(temp > 1 ? g.message(code: 'timeAgo.minutes') : g.message(code: 'timeAgo.minute'))
            }
            res.append(' ').append(g.message(code: 'timeAgo.ago'))
            return res.toString()
        } else {
            return g.message(code: 'timeAgo.justNow')
        }
    }

    /**
     * @emptyTag
     *
     * @attr date the date in past
     */
    def timeAgo = { attrs ->
        Date date = attrs.date
        out << millisToLongDHMS((new Date().getTime()) - date.getTime());
    }

And then you need to add message codes in `messages.properties`:

timeAgo.justNow=just now
timeAgo.minute=minute
timeAgo.minutes=minutes
timeAgo.hour=hour
timeAgo.hours=hours
timeAgo.day=day
timeAgo.days=days
timeAgo.ago=ago
# for Russian
timeAgo.justNow=только что
timeAgo.minute=минуту
timeAgo.minutes=минут
timeAgo.hour=час
timeAgo.hours=часов
timeAgo.day=день
timeAgo.days=дней
timeAgo.ago=назад
# for Ukrainian
timeAgo.justNow=тіки що
timeAgo.minute=хвилину
timeAgo.minutes=хвилин
timeAgo.hour=годину
timeAgo.hours=годин
timeAgo.day=день
timeAgo.days=днів
timeAgo.ago=тому

Avatars in Grails with Gravatar

On every site we often need ability to show user avatars. The simplest way is to use external serive Gravatar.com.

There exists even a plugin for Grails but you don’t need it.
You can use gravatars in gsp files like this:

<img src="https://www.gravatar.com/avatar/${user.email.encodeAsMD5()}"/>

That’s all. But if you want you can create a tag in taglib:

class TagLib {
    static encodeAsForTags = [ gravatar: [taglib: 'none'] ]

    /**
     * Render img tag with user avatar from Gravatar.com
     * @emptyTag
     *
     * @attr user User
     */
    def gravatar = { attrs ->
        User user = attrs.user
        assert user
        out << "<img src='https://www.gravatar.com/avatar/${user.email.encodeAsMD5()}'/>"
    }
}

@EqualsAndHashCode on Grails domains

I found a strange bug\behavior in Grails.

How to grasefully check domains for equality?
For example, we we have User comain class and we want to check in code equality of its two instances:

class User {
}

currentUser == article.author

In this case equals will be by reference.
But this objects may have different references: one was get from DB another was from session. And equals operator will return false.
So, we need to check them for equality by id field:

currentUser.id == article.author.id

But, you can forget about this and still compare by references, so lets generate equals() and hashCode() methods. In IntelliJ we should set cursor on class name and press Alt+Insert.
1. In the dialog we must check option «Accept subclasses» becouse Hibernate inside Grails always creates a proxy subclass of our User class.
2. We should uncheck all fields and leave only one id field

class User {
boolean equals(o) {
if (this.is(o)) return true
if (!(o instanceof User)) return false
User user = (User) o
if (id != user.id) return false
return true
}

int hashCode() {
return id.hashCode()
}
}

But in Groovy 2 we have a cool annotation EqualsAndHashCode

@EqualsAndHashCode(includes = 'id')
class User {
}

But! In this case, condition currentUser == article.author is always return true! Always, when they equal or not. I can understood why always true.
It costs me a few hours of deep debug of transformator EqualsAndHashCodeASTTransformation until I understood a root of problem.
The field id is not exists in class User — it’s wired dynamicly by Grails (i.e GORM).
So, this annotation becomes redundant — it doesn’t make any equals checking on id.
You can write any non existent field in includes and transformer will not fail compilation:

@EqualsAndHashCode(includes = ['id', 'ololo', 'trololo'])
class User {
}

I will rise an issue in bugtracker with asking a developers to make transformer more strictly.

Thus solution to fix is simple: you just need to declare id field explicitly like this:

@EqualsAndHashCode(includes = 'id')
class User {
long id
}

Now all should be fine. Also I remember that had the similar problem with implicit id field with @ToString annotation.
Also the same problem I found with fields like belongsTo:

@EqualsAndHashCode(includes = 'author')
class Article {
static belongsTo = [author: User]
}

This will not work. Again, explicitly declare field:

@EqualsAndHashCode(includes = 'author')
class Article {
User author
static belongsTo = [author: User]
}

This will work as expected with equals.

So rule is simple: always declare field explicitly!

Have a nice coding!

«Comparing JVM Web Frameworks» by Matt Raible

It’s very cool talk about qestions that every Java developer must decide on new project. Overview of all popular Java frameworks from JSF and Struts to Play and Vaadin.

[Grails] Избегайте использования Environment вне файлов конфигураций

Внутри Grails есть великолепный механизм для условного выполнения кода в зависимости от текущей среды (Environment).
Например внутри DataSource.groovy можно указывать различные настройки базы данных:

// environment specific settings
environments {
    development {
        dataSource {
            dbCreate = "create-drop"
            url = "jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000"
        }
    }
    production {
        dataSource {
            dbCreate = "update"
            url = "jdbc:h2:prodDb;MVCC=TRUE;LOCK_TIMEOUT=10000"
        }
    }
}

Наверняка у вас почти в каждом файле конфигурации есть настройки под среду.

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

<g:if env="test"> ... </g:if>

Я постепенно пришёл к тому что этого следует избегать, потому что теряется читаемость и гибкость. Вместо этого лучше явно создать опцию в Config.groovy, включать или выключать её в зависимости от среды а и потом проверять её. Вот например что делает этот код?

    <g:if env="test">
        <meta name="controller" content="${controllerName}"/>
        <meta name="action" content="${actionName}"/>
    </g:if>

На самом деле этот код добавляет имя контроллера и экшена которые отрисовли страницу и они потом проверяются функциональным тестом. Это это бывает очень удобно для дебага.
А теперь давайте мы создадим в файле Config.groovy опцию

environments {
    development {
        com.example.showActionNameInPageMeta = true
    }
    test {
        com.example.showActionNameInPageMeta = true
    }
    production {
        com.example.showActionNameInPageMeta = false
    }
}

и теперь

    <g:if test="${grailsApplication.config.com.example.showActionNameInPageMeta}">
        <meta name="controller" content="${controllerName}"/>
        <meta name="action" content="${actionName}"/>
    </g:if>

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

В более широком смысле такой подход называется Feature flag, и его активно используют например в Amazon.