Статьи

Как сделать свои собственные спички Hamcrest в Kotlin

Эта статья является перепиской более старой, выполненной на Java. Это сделано в Kotlin вместо этого.

Введение в Хэккрест Матчерс

Перво-наперво, я должен быстро объяснить, что такое Matcher Hamcrest. При выполнении модульного тестирования встроенные типы утверждений, которые входят в структуру тестирования, обычно довольно ограничены. Они позволяют человеку легко получить несколько утверждений, чтобы проверить одну вещь. Даже если он не содержит нескольких утверждений, эти утверждения не являются наиболее свободными для чтения и не содержат точных данных о том, что вы проверяете.

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

1
2
3
4
5
@Test
fun testUsingMatcher() {
    val string = methodThatShouldReturnAnEmptyString();
    assertThat(string, isEmptyString());
}

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

Анатомия подколенного сухожилия

Мы пройдемся по основам, воссоздав IsEmptyString сопоставления IsEmptyString . Во-первых, этот сопоставитель будет расширяться от <code-BaseMatcher. С этим, давайте начнем строить.

В основном, в Hamcrest Matcher есть 4 части: метод статической фабрики, утверждение, описание переданного утверждения и описание несостоявшегося утверждения. Последние два являются основной причиной, почему я написал эту статью. Мне потребовалось некоторое время, чтобы немного разобраться в «лучших методах» для этого. Но мы начнем с вершины.

Честно говоря, статический метод фабрики не проблема в Kotlin. основной причиной этого было отсутствие необходимости в new ключевом слове, но Котлин все равно его не использует. Если вы планируете сделать его обратно совместимым для использования в коде Java, я рекомендую по-прежнему использовать его. Если нет, то единственное, с чем вам нужно бороться – это использование заглавных букв. Вы в порядке, когда в утверждении используется заглавное имя класса? Если так, то у вас все просто. Если нет, у вас есть возможность либо сделать имя класса в нижнем регистре (тем самым нарушив соглашение), либо добавить статический метод фабрики. Также всегда есть опция import ... as ... которую так любезно предоставляет Котлин.

Если вы собираетесь создать статическую фабрику, я на самом деле рекомендую делать это как функцию верхнего уровня, а не как companion object . Это автоматически делает его статическим методом для пользователей Java, чтобы они могли выполнять хороший статический импорт, и это позволяет вам избежать странного сопутствующего синтаксиса (особенно в сочетании с @JvmStatic .

Этот код для функции так же прост:

1
fun isEmptyString() = IsEmptyString()

В целом, метод очень мягкий и очевидный. Я еще не столкнулся с примером, где это не так. Это также особенно быстро и просто со всеми сочетаниями клавиш, которые позволяет Kotlin, например, пропуская тип возвращаемого значения и используя форму одного выражения.

Далее нам нужно утверждение, которое делается с помощью метода match (). Это настоящая работа мэтчера, выполняющего фактическую проверку. Обратите внимание, что это не предназначено для выброса AssertionError ; это работа метода assertThat() . Этот метод просто возвращает Boolean указывающее, соответствует ли входная информация идее, которую проверяет Matcher.

Входные данные в этот метод поступают из первого аргумента в assertThat() . Когда assertThat() запускает предоставленный метод assertThat() , он передает этот аргумент в него. Метод matches() принимает Object ( Any в Kotlin), а не тип, указанный обобщениями. Это из-за какой-то странной «особенности» дженериков Java, которую я не изучал, поэтому я не могу это объяснить. Я просто хотел, чтобы вы знали, что вам, как правило, придется выполнять какую-то проверку типов, когда вы просто расширяете BaseMatcher .

Вот реализация нашего IsEmptyString matches() :

1
2
3
4
5
override fun matches(actual: Any?): Boolean =
    if (actual is String)
        actual == ""
    else
        false

Вот и все, что нужно сделать в этом случае – он просто проверяет, является ли это пустой строкой.

Теперь мы должны сделать два описания. Мы сделаем это в тандеме, так как они похожи. Это два описания: descriptionTo describeTo() и descriptionMismatch describeMismatch() . Метод describeTo() используется для описания того, что ожидает лицо, выполняющее сопоставление. В данном случае это пустая String . Метод descriptionMismatch () предназначен для описания фактического результата, обычно просто выводя данный объект.

Два метода описания вступают в действие только в случае сбоя сопоставления и AssertionError . Затем assertThat() создает описание для assertThat() утверждения. Он отформатирован следующим образом:

1
2
3
<user-provided reason>
Expected: <result of describeTo>
     but: <result of describeMismatch>

Причину, указанную пользователем, можно определить, отправив сначала аргумент String в метод assertThat() .

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

Что странно в этих двух методах, так это то, что они не используют String s как таковые. Они используют так называемый объект Description , который, я уверен, позволяет им 1) избегать чрезмерной конкатенации строк, так как кажется, что она работает как StringBuilder и 2) потенциально использовать различные виды Description (это интерфейс). ) объекты для отображения вещей немного по-разному в разных инструментах.

Итак, вот наша реализация этих «описательных» методов:

1
2
3
4
5
6
7
override fun describe(description: Description) {
    description.appendText("empty String")
}
 
override fun describeMismatch(item: Any?, description: Description) {
    description.appendValue(item)
}

Вы заметите, что объект Description имел два метода appendText() и appendValue() . Я только когда-либо использовал эти два, хотя у него есть еще несколько. Это несколько редких случаев, и вы должны проверить их, если вам интересно. Если неясно, appendText возьмет String и добавит его в конец того, что уже есть в описании, как StringBuilder . И appendValue видимому, запускает toString() для всего, что вы передаете, и добавляете это.

До скорого

Это оно! Вы сделали. Теперь у вас есть работающий Hamcrest Matcher. Для получения дополнительной информации, вы должны проверить официальный сайт . Если бы вы IsEmptyString исследованиями, вы бы обнаружили, что мы неправильно воссоздали встроенный IsEmptyString Matcher. Есть несколько техник, которые немного более продвинуты (и это сделало бы этот пост более длинным, чем мне удобно). Я расскажу о них в следующем посте.

Опубликовано на Java Code Geeks с разрешения Джейкоба Циммермана, партнера нашей программы JCG . Смотрите оригинальную статью здесь: Как сделать свои собственные спички Hamcrest в Kotlin

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