Статический анализ Kotlin-приложений #
Документация на статический анализ #
- Проверка и форматирование исходного кода с ktlint
- Плагин для системы сборки Gradle для запуска ktlint
- Статический анализатор кода detekt
Статический анализ исходного кода #
Статический анализ исходного кода применяется для решения следующих задач:
- Нахождение синтаксических и лексических проблем в исходном коде. Реализуется внутри компилятора языка.
- Обеспечение единого оформления для исходного кода.
- Предупреждение потенциальных проблем в исходном коде приложения.
Изучите особенности анализа исходного кода
- Использование статического и динамического анализа для повышения качества продукции и эффективности разработки
- Внедряйте статический анализ в процесс, а не ищите с его помощью баги
- Статические анализаторы кода на примере ClickHouse
Проверка форматирования кода с помощью ktlint #
ktlint — инструмент для статического анализа форматирования исходного кода в соответствии с официальным соглашением о стилистике кода.
Для подключения к проекту необходимо:
- Добавить поддержку инструмента в систему сборки проекта Gradle.
- Добавить настройку стиля программирования в среду сборки IDEA.
- Использовать Gradle-задачи для проверки исходного кода на соответствие стиля.
Добавление поддержки в Gradle #
Инструмент ktlint может быть интегрирован разными способами, рассмотрим использование плагина Ktlint Gradle.
Необходимо добавить плагин в конфигурацию build.gradle.kts:
plugins{
    id("org.jlleitschuh.gradle.ktlint") version "12.1.0"
}Внутри конфигурации необходимо указать версию инструмента. Для этого в файле gradle.properties укажите версию инструмента:
ktlintVersion=1.2.1И в build.gradle.kts необходимо указать используемую версию инструмента:
val ktlintVersion: String by project
ktlint {
    version.set(ktlintVersion)
}Настройка инструмента ktlint #
Ktlint поддерживает несколько стандартов оформления. Настройка производится путём формирования файла .editorconfig, который должен располагаться рядом с файлами конфигурации системы сборки Gradle build.gradle.kts.
В рамках курса необходимо использовать следующую конфигурацию для данного файла:
[*.{kt,kts}]
ktlint_code_style = ktlint_officialЗапуск проверки исходного кода #
После конфигурации можно запускать Gradle-задачу для проверки исходного кода ktlintCheck: ./gradlew ktlintCheck. Все сообщения о нарушениях форматирования необходимо исправить.
Ограничения инструмента:
- В пути к проекту допускается использование только символов латинского алфавита.
- Инструмент может выполнить кеширование результатов
Настройка форматирования в IDEA #
После успешной конфигурации необходимо выполнить в соответствии с официальным руководством. Данная настройка позволит значительно уменьшить количество исправлений в исходном коде, чтобы оно соответствовало требованиям статического анализатора ktlint.
Анализ потенциальных проблем в исходном коде #
Инструмент detekt выполняет поиск потенциальных проблем в исходном коде. Данные проблемы связаны с типичными проблемами:
- Слишком длинные методы.
- Большая вложенность циклов и условных операторов.
- Плохие названия переменных.
- Добавление комментариев к исходному коду и т.д.
Для использования инструмента необходимо:
- Сформировать правила проверки кода.
- Добавить конфигурацию для запуска инструмента в Gradle.
- Использовать Gradle-задачи для проверки исходного кода.
По желанию можно добавить расширение для IDEA.
Формирование набора правил проверки кода #
В настоящий момент никаких отличий относительно базовой конфигурации инструмента не предполагается.
Важно: самостоятельно вносить изменения в конфигурацию инструмента запрещено. В частности запрещено подавлять сообщения от анализатора с помощью аннотаций или baseline-файла.
Добавление поддержки в Gradle #
Инструмент изначально предлагает расширение для Gradle. Для его использования необходимо добавить соответствующий плагин в build.gradle.kts:
plugins {
    id("io.gitlab.arturbosch.detekt") version "1.23.3"
}Далее внутри конфигурации необходимо добавить настройку для проверки кода:
detekt {
    allRules = true
    buildUponDefaultConfig = true
}Запуск проверки исходного кода #
После конфигурации для проверки исходного кода можно запустить задачу detekt: ./gradlew detekt. Все сообщения от инструмента рекомендуется исправлять.
Настройка проверки внутри IDEA #
Проект detekt предоставляет возможность встроить проверку с помощью специального расширения.
Задача № 1 #
Добавьте проверку исходного кода с помощью инструментов ktlint и detekt в рамках исходного кода последней практической работы. Исправьте все проблемы, которые были найдены с помощью
Написание модульных тестов #
Подключение библиотеки Kotest #
Для написания тестов с помощью библиотеки Kotest необходимо добавить зависимости в проект. Внесите следующие доработки в файлы настроек системы сборки проекта.
- Удостовериться, что включена настройка для запуска JUnit-тестов в build.gradle.kts:test { useJUnitPlatform() }
- Добавьте версию библиотеки Kotest в gradle.properties:kotestVersion=5.8.1
- Добавьте библиотеку для запуска Kotest-тестов в список зависимостей для запуска тестов в build.gradle.kts:val kotestVersion: String by project dependencies { testImplementation("io.kotest:kotest-runner-junit5:${kotestVersion}") }
- Добавьте библиотеку для написания тестовых утверждений в build.gradle.kts:dependencies { testImplementation("io.kotest:kotest-assertions-core:${kotestVersion}") }
Написание тестов #
Библиотека Kotest поддерживает множество стилей тестирования. В рамках занятия будем рассматривать исключительно Fun Spec. Для написания тестов можете использовать любой стиль, который понравится.
Перед написанием новых тестов удалите тесты, которые были сгенерированы вместе с шаблоном приложения. Приведение их в рабочий порядок не является целью данного занятия.
В качестве примера опишем тест для класса треугольник, имеющий следующий интерфейс.
package ru.yarsu.domain
data class Triangle(val sideA: Double, val sideB: Double, val sideC: Double) {
    fun area(): Double { ... }
    fun perimetr(): Double { ... }
}В каталоге src/test/kotlin создадим пакет ru.yarsu.doman, в котором разместим тест для класса треугольник. Пока что создадим
package ru.yarsu.domain
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.shouldBe
class TriangleTest : FunSpec({
    test("test the length") {
        "test".length.shouldBe(5)
    }
})Обратите внимание на способ написания теста. Класс теста наследуется от класса FunSpec, которому в качестве аргумента передаётся лямбда-вырежение. Внутри данного выражения доступны методы для описания тестов, их расширенного контекста и так далее.
Запустите тесты с помощью команды test системы сборки Gradle: ./gradlew test (или через систему запуска команд IDEA gradle test).
Удостоверьтесь, что тест не проходит. Исправьте его, чтобы он проходил.
Добавим тесты для проверки работы методов класса треугольник:
test("Should calculate perimeter") {
    val triangle = Triangle(5.0, 4.0, 3.0)
    triangle.perimeter().shouldBe(12.0.plusOrMinus(0.01))
}
test("Should calculate area") {
    val triangle = Triangle(5.0, 4.0, 3.0)
    triangle.area().shouldBe(6.0.plusOrMinus(0.01))
}Изначальный тест, “test the length”, можно удалить.
Описание тестовых утверждений #
Библиотека Kotest предоставляет свой набор утверждений для удобного описания требований к работе тестируемого кода. В рамках конфигурации была добавлена библиотека Core Matchers, которая включает в себя большое количество тестовых утверждений для классов и данных из стандартной библиотеки языка Kotlin.
Особенности запуска тестов #
Библиотека Kotest по умолчанию создаёт класс теста один раз для всех тестов, которые описаны внутри него. В результате надо аккуратно относиться к состоянию, которое доступно всем тестам: запуск отдельных тестов не должен влиять на данное состояние. Если вы хотите получить поведение, аналогичное JUnit, то необходимо настроить режим изоляции.
Задача № 2. Написание тестов для класса Triangles #
Создайте класс для тестирования всех методов класса Triangles, который был разработан для хранения списка треугольников.
Тестирование http4k-приложений #
При разработке веб-приложения в рамках модульных тестов можно тестировать логику следующих компонентов:
- Обработчики HTTP-запросов.
- Фильтры.
- Классы предметной области.
- Отдельные компоненты.
Для обеспечения быстроты написания тестов с помощью Kotest разработчики библиотеки http4k предоставляют свои наборы тестовых утверждений.
- Краткое описание тестовых утверждений доступно на соответствующей странице.
- Список функций в API библиотеки.
Для поддержки данных выражений в шаблоне приложения уже добавлена библиотека
dependencies {
    testImplementation("org.http4k:http4k-testing-kotest:${http4kVersion}")
}Написание тестов для HTTP-обработчиков #
Разработаем тест для HTTP-обработчика корневого маршрута. Данный HTTP-обработчик формирует статический HTML-документ и не получает никаких параметров.
Создадим соответствующий класс-тест:
package ru.yarsu.web.handlers
import io.kotest.core.spec.style.FunSpec
import io.kotest.matchers.comparables.shouldBeGreaterThan
import io.kotest.matchers.should
import io.kotest.matchers.shouldNotBe
import org.http4k.core.Method
import org.http4k.core.Request
import org.http4k.core.Status
import org.http4k.kotest.haveStatus
class HomeHandlerTest : FunSpec({
})Сформируем тест на проверку ключевой функциональности данного обработчика:
- Создадим обработчик HTTP-запроса.
- Создадим объект запроса.
- Вызовем созданный обработчик.
- Проверим свойства возвращённого объекта.
test("Should respond with non-empty body and good status") {
    val request = Request(Method.GET, "/")
    val handler = HomeHandler()
    val response = handler(request)
    response should haveStatus(Status.OK)
    response.body.length shouldNotBe null
    response.body.length?.shouldBeGreaterThan(1L)
}Задача № 3. Тестирование HTTP-обработчика для показа списка треугольников #
Разработайте тесты для HTTP-обработчика для показа списка треугольников. Рассмотрите следующие случаи:
- Передан наполненный список треугольников.
- Передан пустой список треугольников.
Для каждого теста определите утверждения для проверки.
Написание тестов HTTP-обработчиков #
Обработчики HTTP-запросов, разработанные на прошлом занятии, в качестве зависимости имеют список треугольников. Для написания тестов необходимо правильным образом инициализировать данный класс, наполнить его данными. В текущей ситуации класс достаточно простой, но при усложнении приложения инициализация зависимостей будет усложнять логику тестов.
В качестве решения данной проблемы предлагается ввести слой объектов-операций, которые предоставляют доступ к одной операции над хранилищем данных. Для описания данных объектов можно воспользоваться интерфейсами или привязанными функции и свойствами. Пример с интерфейсами был представлен в рамках лекции
Рассмотрим следующий пример использования привязанных фукнций:
class Triangle(val sideA: Double, val sideB: Double, val sideC: Double) {
    fun perimeter() = sideA + sideB + sideC
    fun enlarge(coefficient: Double) = Triangle(
        coefficient * sideA,
        coefficient * sideB,
        coefficient * sideC,
    )
}Проверим работу привязанной функции.
test("Should create bound function") {
    val triangle = Triangle(3.0, 4.0, 5.0)
    val enlargeFunction: (Double) -> Triangle = triangle::enlarge
    val newTriangle = enlargeFunction(2.0)
    newTriangle.perimeter().shouldBe(24.0.plusOrMinus(0.01))
}Для метода enlarge класса Triangle создадим объект, который будет являться функциональным типом, сигнатура которого совпадает с сигнатурой оригинального метода.
Задача № 4. Выделение операции по получению списка треугольников #
Выделите в приложении операцию по получению списка треугольников. Измените обработчик HTTP-запроса так, чтобы он принимал в качестве аргумента конструктора ссылку на данную операцию. Переработайте тесты для данного обработчика, чтобы они успешно проходили.