Без хайпа и маркетинга: нужен ли вам Kotlin?
В рамках QAFest 2017 автор блога automation-remarks.com и Lead QA Automation Engineer в Ciklum Сергей Пирогов рассказал, как он докатилися до Kotlin и что из этого вышло. Для читателей DOU, которые не присутствовали на конференции, Сергей изложил этот опыт в авторской статье.
Меня зовут Сергей Пирогов. Уже более пяти лет я занимаюсь вопросами автоматизации тестирования на проектах различного масштаба и сложности. В основном я занимаюсь автоматизацией тестирования с использованием технологий из мира Java.
Kotlin на текущий момент однозначно находится на пике популярности и в тестировании может принести немало пользы. Но не все так просто, в чем вы можете убедиться из этой статьи.
Предыстория: Java, Groovy и другие альтернативы
Java является самым старым и популярным языком на JVM. Отличный язык и инфраструктура. Бесспорный фаворит различных рейтингов среди языков программирования. На Scala мы проектов писать не пробовали из-за излишней сложности, а вот на Groovy писали и достаточно успешно. Этим опытом я делился на конференции Selenium Camp 2016. Увы, Groovy умирает и появление Kotlin его агонию лишь ускоряет.
Откуда взялся этот ваш Kotlin?
Kotlin — достаточно молодой язык, который разрабатывается и спонсируется компанией JetBrains. Из открытых источников можно узнать, что в разработку языка было вложено более $15 млн, а сам язык — это еще один способ популяризовать компанию и еще больше повысить продажи для Idea.
Язык начал набирать популярность после того, как на конференции JavaOne 2015 Hans Dockter, CEO of Gradle, заявил, что Kotlin получает официальную поддержку для написания Gradle билд-скриптов. Тогда Kotlin все еще был в бете, но новость всколыхнула всех неравнодушных. Волна хайпа начала подниматься уже в тот момент. На пике популярности язык оказался в мае этого года на конференции Google I/O, где было объявлено о том, что Kotlin наряду с Java становится официальным языком разработки под платформу Android. Тут-то всех и «лупануло»: весь Twitter был в постах о Kotlin, появилась куча блог-постов с признаниями в любви языку. Представители JetBrains в различных источниках стали заявлять, что Kotlin — это будущее разработки на JVM.
В целом если смотреть на ситуацию здраво, то причины хайпа вполне понятны. Джава развивается слишком медленно. Java 8 появилась аж в 2014 году, Java 9 на момент публикации уже вышла, но в самом языке слишком мало вкусных фишек. Более того, с Java 9 у многих все в момент перестало работать. И тут людям дают язык, наполненный фичами, часть из которых появится только в
Чем этот ваш Kotlin круче?
Авторы языка признаются, что не пытались придумать что-то кардинально новое. Язык специально задумывался максимально прагматичным и удобным в использовании для разработчиков. Вот небольшой список фишек, которые есть в Kotlin:
C более полным списком можно ознакомиться по ссылке.
И что? Как оно помогает жить?
Null safety — это selling point Kotlin. Проверка на nullable type осуществляется еще во время компиляции.
var a: String = "abc" a = null // compilation error var b: String? = "abc" b = null // ok
Это очень удобно и помогает избежать многих багов.
Extension functions — это фича, которой мне лично очень не хватает в Java. Ниже пример, как с помощью всего пары функций можно улучшить существующий Selenium API.
fun WebDriver.open(url: String) { get(url) } fun WebDriver.all(cssselector: String): MutableList<WebElement>? { return findElements(By.cssSelector(cssselector)) } fun List<WebElement>.shouldHave(size: Int) { assert(this.size == size) }
По итогу мы можем писать тесты в таком формате:
val driver = ChromeDriver() driver.open("http://automation-remarks.com") driver.all(".post").shouldHave(size = 9)
Еще одной фишкой являются функции типа .apply
, .with
. C их применением можно сделать код более компактным.
ChromeDriver().apply { open("http://automation-remarks.com") all(".post").shouldHave(size = 9) }
String template — позволяет удобно форматировать строки. Мы очень часто пользуемся этим в тестах.
fun findCustomerById(id:Long): CustomerEntity{ val query = """ SELECT * FROM customer WHERE id = $id; """ return findOne(CustomerEntity::klass, query) }
Как видите, SQL запрос не содержит уродливых переносов строк и конкатенаций. Его просто читать, копировать и редактировать.
Reified type — фишка, которая позволяет сделать ваш код очень красивым и лаконичным. Например, мы в тестах часто пользуемся библиотекой Apache DBUtils. С ее применением код получается таким:
val beanHandler = BeanHandler<City>(City::class.java) val city: City = QueryRunner().query("SELECT * FROM city", beanHandler)
Но, применив уже знакомые нам extension function
и reified type
, мы можем сделать так:
inline fun <reified T> QueryRunner.findOne(sql: String): T { return BeanHandler(T::class.java).run { query(sql, this) } }
и получить следующий код:
val city : City = QueryRunner().findOne("SELECT * FROM city")
В этом случае тип, в который конвертируется результат запроса, будет браться из типа объявленной переменной. Это гораздо удобнее, чем Java Generics.
Совместимость с Java
Из официальной документации известно, что Kotlin разрабатывался с оглядкой на максимальную совместимость с Java. Java Interop подается под соусом, что мы можем взять любой код, написанный раньше, и вызвать в Kotlin. Либо же обратно — няшный Kotlin-код вызвать в унылой джавке.
Так ли это на самом деле? Давайте разбираться.
Kotlin vs Rest Assured
Для написания тестов для API мы используем одну из лучших библиотек — Rest Assured. Посмотрим, как она будет работать в Kotlin.
Опа-опа, when
зарезервированное слово. Обойти такое ограничение можно, обернув его в такие вот интересные кавычки. Скажу честно, я даже не сразу нашел эти символы на своей клавиатуре :-)
Kotlin + Selenide
Для написания UI тестов мы используем Selenide. Давайте посмотрим на совместимость.
И снова неудача — $
нельзя, val
тоже нельзя.
Kotlin + Hamcrest (AssertJ)
Все мы при написании тестов активно используем такие библиотеки, как Hamcrest и AssertJ. Что с совместимостью?
Здесь нас тоже ждут ограничения.
Все через костыли
Когда начинаешь натыкаться на такие ограничения, то напрашивается вполне логичная мысль...
Чиним Kotlin и Rest Assured
На самом деле все предыдущие примеры можно в какой-то степени починить. Смотрим на пример с Rest Assured:
fun RequestSpecification.When(): RequestSpecification { return this.`when`() } @Test fun basicPingTest() { given() .When() .get("/garage") .then().statusCode(200); }
Делаем extension function
, в который оборачиваем вызов when
. Работает? Работает. Да, это костыль, но рабочий.
Чиним Kotlin vs Selenide
В случае с Selenide нам нужно просто обернуть вызовы функций $
и $$
, a вместо .val()
вызывать .setValue()
.
fun get(selector: String) : SelenideElement { return `$`(selector); } fun all(selector: String) : ElementsCollection { return `$$`(selector); }
Результат:
@Test fun usingDollarsWithBackticks() { get(By.name("q")).setValue("selenide") all("#ires .g").shouldHave(size(10)) get("#ires .g").shouldHave(text("Kotlin")); }
Чиним Kotlin + Hamcrest (AssertJ)
Увы, по этому пункту нас ждет разочарование. Если Hamcrest еще как-то совместим с Kotlin, то AssertJ починить не получится из-за несовместимости в Generic types. Здесь нам нужно просто взять и заменить библиотеку. Благо, в GitHub уже есть энтузиасты, которые написали порт — assertk.
@Test fun example(){ assertThat(1).isEqualTo(1) } assert { throw Exception("error") }.throwsError { it.hasMessage("wrong") } // -> expected [message] to be:<["wrong"]> but was:<["error"]>
Следует отметить, что assertk обладает более удобным API и полностью совместима с Kotlin.
Вроде бы все наши проблемы мы «подлечили», ну или хотя бы подставили костыли. Естественно, вы можете не натолкнуться на проблемы, приведенные выше, если на старте проекта будете выбирать библиотеки и технологии, совместимые с Kotlin.
Чем же все-таки хАрош Kotlin?
В дополнение к языковым фичам и синтаксическим конструкциям, могу отметить, что язык очень лаконичный и позволяет строить удобные DSL. В подтверждение покажу пример теста, написанного с применением библиотеки Kirk, которая призвана заменить Selenide для Kotlin.
Пример четко демонстрирует, какого формата DSL можно писать. По сути — это BDD, только в коде, со всеми плюшками в виде строгой типизации, автодополнениями и поддержкой рефакторинга.
Еще одним примером является Gradle Kotlin-DSL. Уже сейчас gradle.build файл можно писать более приятным способом, получая автодополнения и статическую компиляцию. Эта фича пока что не достигла стадии релиза, но я больше чем уверен: когда будет 1.0, Groovy DSL можно будет помахать ручкой и полностью перейти на Kotlin.
Что имеем в итоге?
Kotlin — очень приятный язык. Все, что уже реализовано у конкурентов Java, в нем есть. Конвертировать существующий код на Java в Kotlin немного проблематично. Нет еще пока полной совместимости со всеми самыми популярными Java-фреймворками и библиотеками. Лично я получаю удовольствие от работы с языком. В заключение я не буду давать явных советов — писать или не писать, учить Kotlin или хейтить и идти учить JavaScript. Я просто оставлю вам ссылочку на свежий репорт от Rebel Labs о состоянии Java-экосистемы, в котором Kotlin назван самым любимым языком c коэффициентом удовлетворенности 9.1 из 10.
Спасибо, что читали. Всегда думайте головой, не ведитесь на хайп и маркетинг. Выбирайте технологии под свои задачи.