Статический анализ 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-запроса так, чтобы он принимал в качестве аргумента конструктора ссылку на данную операцию. Переработайте тесты для данного обработчика, чтобы они успешно проходили.