
Автоматизация тестирования класса данных Kotlin с помощью KSP
23 июля 2025 г.Вы когда -нибудь обнаруживали, что вам нужно создать множество экземпляров класса данных с немного разными параметрами? Возможно, для тестирования, генерации данных выборки или заполнения пользовательского интерфейса различными состояниями. Их создание вручную может быть утомительным и подверженным ошибкам. Что если бы мы могли автоматизировать это?
Это была искра, которая привела меня к изучению обработки символов Kotlin (KSP) и Kotlinpoet для создания Kombinator, процессора аннотации, который автоматически генерирует все возможные комбинации параметров конструктора класса данных.
В этой статье я проведу вас через процесс, дизайнерские решения и некоторые из интересных проблем, с которыми сталкиваются при строительстве Kombinator.
Цель: Что такое Комбинтор?
По своей сути Kombinator стремится упростить создание нескольких экземпляров класса данных Kotlin. Вы аннотируете класс данных (или его параметры)Комбини предоставить возможные значения для каждого параметра. Затем Кобинатор генерирует объект, содержащий свойства для каждой уникальной комбинации этих параметров, плюс удобныйgetallcombinations ()Функция, чтобы получить их все в качестве списка.
Например, представьте себеПользователиКласс данных:
https://gist.github.com/sarimmehdi/d30182d8e755464ca6dbd1a06977e123?embedable=true#file-usersettings-kt
С Комбинатором вы можете аннотировать это так:
https://gist.github.com/sarimmehdi/fcbf995245a8463792131045369d210b?embedable=true#file-usersettingskombine-kt
Кобинатор тогда генерирует объект, скажем,Пользовательские работы, смотреть что -то подобное (упрощено):
https://gist.github.com/sarimmehdi/1f63835ac9a2d2c1bdfbb91d8a7ac3f12?embedable=true#file-usersettingscombinations-kt
Инструменты: KSP и Kotlinpoet
- Обработка символов Kotlin (KSP): KSP - это API, разработанный Google, который позволяет писать легкие плагины компилятора для Kotlin. Он обрабатывает код Kotlin во время компиляции, обеспечивая доступ к структуре вашего кода (например, классы, функции, параметры и аннотации), не требуя углубления сложностей внутренних групп Kotlin Compiler. Это делает его более эффективным и стабильным, чем старый KAPT (инструмент обработки аннотаций Kotlin).
- Kotlinpoet: От Square, Kotlinpoet - это фантастическая библиотека для генерации исходных файлов .kt. Он предоставляет жидкий API для программного построения кода Kotlin, прекрасно обработки импорта, форматирования и других деталей.
Путешествие: строительство кобинатора
Давайте разберемся, как работает Комбинатор и ключевые компоненты.
- Комбин аннотация
Это точка входа. Это аннотация удержания источника, что означает, что она присутствует только во время компиляции и не попадает в последний байт -код. Он нацелен на классы и параметры значения.
https://gist.github.com/sarimmehdi/ecb4e858ea9761ff1d2cb54be9afb3b6?embedable=true#file-kombine-kt
Важный выбор дизайна здесь состоял в том, чтобы иметь отдельные параметры массива для каждого поддерживаемого типа данных (AllpossiblestringParamsВAllPossibleIntParams, и т. д.). Это делает его типом безопасным на сайте использования аннотаций и упрощает анализ в процессоре. Первоначально я рассматривал более общий подход, но эта явность оказалась более надежной.
2Процессор и процессор
KSP работает, обнаруживSymbolprocessorProviderреализации. НашProcessorProviderпросто:
https://gist.github.com/sarimmehdi/22cd3e8046ae3e5db32eff37dfab1820?
Это просто создает экземпляр нашего основного процессора. Процессор, где происходит магия:
https://gist.github.com/sarimmehdi/e0a912b815513d4777d038753cc59687?embedable=true#file-processor-kt
Метод процесса является точкой входа для KSP. Это:
- Получает квалифицированное имя нашегоКомбинаннотация.
- Использует резольвер, чтобы найти все символы (классы, параметры и т. Д.)КомбинПолем
- Фильтры для действительногоKsclassdeclarationэкземпляры.
- Вызывает аDataClassvisitorДля каждого аннотированного класса.
3DataClassvisitor: Просмотр и сборы информации
DataClassvisitor (реализованный как внутренний класс для доступа крегистраториCodegenerator) использует шаблон посетителя KSP, чтобы пересечь AST аннотированного класса.
https://gist.github.com/sarimmehdi/a81c4c295f1bee62bba13590ca73cb53?
Помощник класса данных,Constructorparameterinfo, было важно для организации извлеченных деталей о каждом параметре конструктора:
https://gist.github.com/sarimmehdi/abd4241df9219d7f0ca61476c2e2d758?
4Чтение значений аннотаций и комбинируемых групп здания
Вот где логика становится интересной. Нам нужно определить, какие значения объединить для каждого параметра.
- Логические: Для логических параметров без значений по умолчанию мы автоматически предполагаем [false, true] в качестве комбинируемых значений.
- Перечисление: Для параметров Enum без значений по умолчанию мы извлекаем все записи Enum.
- Другие типы (из Комбина): Это обрабатываетсяReadParameterфункция Ключевым принципом дизайна здесь является то, что еслиКомбинАннотация присутствует непосредственно на параметре конструктора, его указанные значения всегда будут иметь приоритет над любыми значениями, определенными на уровне классаКомбинАннотация для того же типа параметров. Это допускает мелкозернистый контроль.
- Обработка значений по умолчанию: Важно отметить, что Kombinator предназначен для создания комбинаций на основе явно предоставленных значений или неотъемлемых вариантов (например, логических и перечислений). Если параметр имеет значение по умолчанию в конструкторе класса данных и явно не нацелен наКомбинАннотация (либо на уровне параметра, либо на уровне класса со значениями для его типа), Kombinator не будет автоматически включать его в состав комбинации. Ожидается, что вы явно определяете диапазон значений, которые вы хотите объединить для параметров, которые вы заинтересованы в изменении. Если параметр со значением по умолчанию нацелен наКомбинЗатем будут использоваться значения от аннотации, переоценивая по умолчанию для сгенерированных комбинаций.
https://gist.github.com/sarimmehdi/c8ebdc9531298dfa8a3f078c1b0169f1?embedable=true#file-dataclassvisitor-kt
АReadParameterфункция (отReadparameter.kt) имеет решающее значение. Он итерации через параметры конструктора, которые не являются логическими, перечисленными и, что важно, рассматривают только те, которые не имеют значений по умолчанию, если они явно аннотированы сКомбинпредоставить ценности. Это связано с тем, что цель состоит в том, чтобы объединиться на основе предоставленных наборов возможностей, а не просто использовать один дефолт.
https://gist.github.com/sarimmehdi/ac8a5f0c4015cfbb369d85e326825253b?embedable=true#file-readparameter-kt
Иreadnotationarrayargumentутилита помогает извлечь значения изКомбинАннотация на массив аргументов:
https://gist.github.com/sarimmehdi/d5efb006624e85fdf18e8a617487527b?embedable=true#file-readanotationarrayargument-kt
Задача - без подписных типов: Примечательная задача возникла с безписанными типами (Ubyte, Ushort и т. Д.). Когда KSP считывает значения из аннотации, таких как val allpossiblebyteparams: ubytearray = [1u, 2u], аргумент. Это требовало тщательного литья и логики конверсии в рамках лямбды заклинателя, переходящей в RearnanotationArrayArgument.
5Создание кода с помощью Kotlinpoet
Как только у нас будетCombinableParameterGroups(Список пар, где каждая параConstructorparameterinfoи этоСписок <any>возможных значений), мы можем начать генерировать код. Это обрабатываетсяWritepropertiesиgenerateCodeУтилита функций.
Writeproperties: Эта функция итерации через все возможные комбинации и создает KotlinpoetPropertySpecдля каждого.
https://gist.github.com/sarimmehdi/9289de7eff82be8b3fa41a837bccc7c5?
GenerateInstanceProperty: Это создает фактическое PropertySpec для одного экземпляра.
https://gist.github.com/sarimmehdi/4079f4ecaacc82178d93fa4989609b96?
Задача - литералы Kotlinpoet: Kotlinpoet мощный, но вам нужно использовать правильные спецификаторы формата для литералов. %S для строк (добавляет котировки), %l для общих литералов, но плавания нуждаются в суффиксе F (SO %LF), Chars нуждаются в отдельных цитатах (« %l»), и без знака нуждаются в суффиксе U (достигается с %LU после того, как подготовила ценность KSP в его подписанный тип подписанного подписанного типа, а затем на долгое время для Kotlinpoet's %. Это требовало тщательного строительстваКодовые блокиПолем
generateCode: Наконец, эта функция собирает сгенерированный объект, добавляетgetallcombinations ()функция и записывает файл.
https://gist.github.com/sarimmehdi/6ccd8026b4fee6017a5243f13d1be4cc?embedable=true#file-generatecode-kt
Управление зависимостью с KSP: Объект зависимостей важен.Зависимости (агрегация = false, файл)сообщает KSP, что сгенерированный файл зависит от исходного файла (файла), который содержит аннотированный класс. Если исходный файл изменяется, KSP знает, что ему нужно переукачать и потенциально регенерировать этот вывод. Агрегация = false означает, что этот процессор не агрегирует информацию из нескольких исходных файлов для создания одного выхода.
6Регистрация и обработка ошибок
На протяжении всего процесса, используя KSPlogger (logger.error ()Вlogger.warn ()Вlogger.info ()) имеет решающее значение для предоставления отзывов пользователю о том, что делает процессор, любые ошибки или ошибки. Чистые сообщения об ошибках являются ключом к хорошему опыту разработчика.
Проблемы и знания
- Понимание типов KSP против типов Kotlinpoet: KSP предоставляет свои собственные представления типов (KstypeВKSDeclaration) Они часто должны быть преобразованы в типное имя Kotlinpoet, используя такие расширения, какTotyPename ()от com.squareup.kotlinpoet.ksp.totypename.
- Чтение аргументов аннотации: Доступ к аннотациям аргументов (Annotation.arguments) и их ценности (Аргумент.value) прост, но тип аргумента. Значение иногда может быть немного удивительным (например, список <Любые> для массивов, проблема без знака, упомянутая ранее). Надежная кастинг и проверка типов необходимы.
- Итеративная комбинированная логика: Алгоритм вWritepropertiesДля итерации через все комбинации (индексы увеличения, очень похожие на подсчет в разных основаниях) является классической комбинаторной проблемой.
- Kotlinpoet Formatting: МастерКодовые блокии спецификаторы формата ( %n для имен, %t для типов, %l для литералов, %s для строк) является ключом к созданию чистого, правильного кода котлина. Согласованные (⇥) и непреднамеренные (⇤) символы очень полезны для читаемости.
Заключение
Строительство Комбинатора было полезным погружением в мир KSP и Kotlinpoet. Он продемонстрировал, как эти инструменты можно использовать для значительного сокращения шаблонов и автоматизации повторяющихся задач кодирования. Несмотря на то, что были, безусловно, были препятствия, особенно вокруг обработки типов и нюансов API KSP, конечным результатом является утилита, которая может действительно сэкономить время и усилия.
Если вы хотите автоматизировать генерацию кода в своих проектах Kotlin, я настоятельно рекомендую изучить KSP. Кривая обучения управляемо, а мощность, которую она предлагает, является существенной.
Счастливого кодинга (и Kombining)!
GitHub:https://github.com/sarimmehdi/kombinator
Оригинал