Использование инструментов проверки кода, таких как lint , может помочь вам найти проблемы и улучшить ваш код, но инструменты проверки могут лишь сделать вывод. Идентификаторы ресурсов Android, например, используют int
для идентификации строк, графики, цветов и других типов ресурсов, поэтому инструменты проверки не могут определить, когда вы указали строковый ресурс там, где следовало указать цвет. Эта ситуация означает, что ваше приложение может отображаться неправильно или вообще не запускаться, даже если вы используете проверку кода.
Аннотации позволяют предоставлять подсказки для инструментов проверки кода, таких как lint, чтобы помочь обнаружить эти более тонкие проблемы кода. Аннотации добавляются в виде тегов метаданных, которые вы прикрепляете к переменным, параметрам и возвращаемым значениям для проверки возвращаемых значений метода, переданных параметров, локальных переменных и полей. При использовании со средствами проверки кода аннотации могут помочь обнаружить такие проблемы, как исключения нулевого указателя и конфликты типов ресурсов.
Android поддерживает различные аннотации через библиотеку аннотаций Jetpack . Вы можете получить доступ к библиотеке через пакет androidx.annotation
.
Примечание. Если модуль зависит от обработчика аннотаций, для добавления этой зависимости необходимо использовать конфигурацию зависимостей kapt
или ksp
для Kotlin или конфигурацию зависимостей annotationProcessor
для Java.
Добавьте аннотации в свой проект
Чтобы включить аннотации в своем проекте, добавьте зависимость androidx.annotation:annotation
в свою библиотеку или приложение. Любые добавляемые вами аннотации проверяются при запуске проверки кода или задачи lint
.
Добавьте зависимость библиотеки аннотаций Jetpack.
Библиотека Jetpack Annotations опубликована в репозитории Google Maven . Чтобы добавить библиотеку аннотаций Jetpack в свой проект, включите следующую строку в блок dependencies
вашего файла build.gradle
или build.gradle.kts
:
Котлин
dependencies { implementation("androidx.annotation:annotation:1.9.0") }
классный
dependencies { implementation 'androidx.annotation:annotation:1.9.0' }
Если вы используете аннотации в своем собственном библиотечном модуле, они включаются как часть артефакта Android Archive (AAR) в формате XML в файл annotations.zip
. Добавление зависимости androidx.annotation
не создает зависимости для последующих пользователей вашей библиотеки.
Примечание. Если вы используете другие библиотеки Jetpack, вам, возможно, не потребуется добавлять зависимость androidx.annotation
. Поскольку многие другие библиотеки Jetpack зависят от библиотеки аннотаций, возможно, у вас уже есть доступ к аннотациям.
Полный список аннотаций, включенных в репозиторий Jetpack, можно найти в справочнике по библиотеке аннотаций Jetpack или использовать функцию автозаполнения, чтобы отобразить доступные параметры import androidx.annotation.
заявление.
Запускайте проверки кода
Чтобы начать проверку кода из Android Studio, которая включает проверку аннотаций и автоматическую проверку кода, выберите в меню «Анализ» > «Проверить код» . Android Studio отображает сообщения о конфликтах, чтобы указать на потенциальные проблемы, когда ваш код конфликтует с аннотациями, и предложить возможные решения.
Вы также можете применить аннотации, запустив задачу lint
с помощью командной строки . Хотя это может быть полезно для обозначения проблем с сервером непрерывной интеграции, задача lint
не применяет аннотации нулевых значений (описанные в следующем разделе); только Android Studio делает это. Дополнительную информацию о включении и запуске проверок на ворс см. в разделе Улучшение кода с помощью проверок на ворс .
Хотя конфликты аннотаций вызывают предупреждения, эти предупреждения не препятствуют компиляции вашего приложения.
Аннотации недействительности
Аннотации неопределенных значений могут быть полезны в коде Java для обеспечения того, могут ли значения быть нулевыми. Они менее полезны в коде Kotlin, поскольку Kotlin имеет встроенные правила обнуления, которые применяются во время компиляции. Добавьте аннотации @Nullable
и @NonNull
чтобы проверить неопределенность заданной переменной, параметра или возвращаемого значения. Аннотация @Nullable
указывает переменную, параметр или возвращаемое значение, которое может иметь значение NULL. @NonNull
указывает переменную, параметр или возвращаемое значение, которое не может быть нулевым.
Например, если локальная переменная, содержащая нулевое значение, передается в качестве параметра методу с аннотацией @NonNull
, прикрепленной к этому параметру, при построении кода генерируется предупреждение, указывающее на ненулевой конфликт. Кроме того, попытка сослаться на результат метода, отмеченного @Nullable
без предварительной проверки того, является ли результат нулевым, генерирует предупреждение о нулевом значении. Используйте @Nullable
для возвращаемого значения метода только в том случае, если каждое использование метода должно быть явно проверено на ноль.
Следующий пример демонстрирует возможность обнуления в действии. В примере кода Kotlin не используется аннотация @NonNull
, поскольку она автоматически добавляется в сгенерированный байт-код, когда указан тип, не допускающий значения NULL. В примере Java используется аннотация @NonNull
для параметров context
и attrs
чтобы проверить, что переданные значения параметров не равны нулю. Он также проверяет, что сам метод onCreateView()
не возвращает значение null:
Котлин
... /** Annotation not used because of the safe-call operator(?)**/ override fun onCreateView( name: String?, context: Context, attrs: AttributeSet ): View? { ... } ...
Ява
import androidx.annotation.NonNull; ... /** Add support for inflating the <fragment> tag. **/ @NonNull @Override public View onCreateView(String name, @NonNull Context context, @NonNull AttributeSet attrs) { ... } ...
Анализ обнуляемости
Android Studio поддерживает анализ нулевых значений для автоматического вывода и вставки аннотаций нулевых значений в ваш код. Анализ допустимости значений NULL сканирует контракты во всей иерархии методов вашего кода, чтобы обнаружить:
- Вызов методов, которые могут возвращать значение null.
- Методы, которые не должны возвращать значение null.
- Переменные, такие как поля, локальные переменные и параметры, которые могут иметь значение NULL.
- Переменные, такие как поля, локальные переменные и параметры, которые не могут содержать нулевое значение.
Затем анализ автоматически вставляет соответствующие нулевые аннотации в обнаруженные местоположения.
Чтобы запустить анализ на недействительность в Android Studio, выберите «Анализ» > «Вывести недействительность» . Android Studio вставляет аннотации Android @Nullable
и @NonNull
в обнаруженные места вашего кода. После выполнения нулевого анализа рекомендуется проверить введенные аннотации.
Примечание. При добавлении нулевых аннотаций функция автозаполнения может предлагать аннотации IntelliJ @Nullable
и @NotNull
вместо нулевых аннотаций Android и может автоматически импортировать соответствующую библиотеку. Однако средство проверки ворса Android Studio ищет только нулевые аннотации Android. При проверке ваших аннотаций убедитесь, что ваш проект использует нулевые аннотации Android, чтобы средство проверки ворса могло правильно уведомить вас во время проверки кода.
Аннотации ресурсов
Проверка типов ресурсов может быть полезна, поскольку ссылки Android на ресурсы, такие как рисуемые и строковые ресурсы, передаются как целые числа.
Код, который ожидает, что параметр будет ссылаться на определенный тип ресурса, например String
, может быть передан ожидаемому ссылочному типу int
, но на самом деле ссылается на другой тип ресурса, например ресурс R.string
.
Например, добавьте аннотации @StringRes
, чтобы проверить, содержит ли параметр ресурса ссылку на R.string
, как показано здесь:
Котлин
abstract fun setTitle(@StringRes resId: Int)
Ява
public abstract void setTitle(@StringRes int resId)
Во время проверки кода аннотация генерирует предупреждение, если в параметре не передана ссылка R.string
.
Аннотации для других типов ресурсов, таких как @DrawableRes
, @DimenRes
, @ColorRes
и @InterpolatorRes
, можно добавлять с использованием того же формата аннотаций и запускать во время проверки кода.
Если ваш параметр поддерживает несколько типов ресурсов, вы можете поместить более одной аннотации типа ресурса к данному параметру. Используйте @AnyRes
, чтобы указать, что аннотированный параметр может быть любым типом ресурса R
Хотя вы можете использовать @ColorRes
, чтобы указать, что параметр должен быть ресурсом цвета, целое число цвета (в формате RRGGBB
или AARRGGBB
) не распознается как ресурс цвета. Вместо этого используйте аннотацию @ColorInt
, чтобы указать, что параметр должен быть целым числом цвета. Инструменты сборки помечают неправильный код, который передает идентификатор ресурса цвета, например android.R.color.black
, а не целое число цвета, в аннотированные методы.
Аннотации к теме
Аннотации потока проверяют, вызывается ли метод из потока определенного типа. Поддерживаются следующие аннотации потоков:
Инструменты сборки рассматривают аннотации @MainThread
и @UiThread
как взаимозаменяемые, поэтому вы можете вызывать методы @UiThread
из методов @MainThread
и наоборот. Однако поток пользовательского интерфейса может отличаться от основного потока в случае системных приложений с несколькими представлениями в разных потоках. Поэтому вам следует аннотировать методы, связанные с иерархией представлений приложения, с помощью @UiThread
и аннотировать только методы, связанные с жизненным циклом приложения, с помощью @MainThread
.
Если все методы в классе имеют одинаковые требования к потокам, вы можете добавить к классу одну аннотацию потока, чтобы убедиться, что все методы в классе вызываются из одного и того же типа потока.
Обычно аннотации потоков используются для проверки того, что методы или классы, помеченные @WorkerThread
вызываются только из соответствующего фонового потока.
Аннотации ограничений значений
Используйте аннотации @IntRange
, @FloatRange
и @Size
для проверки значений переданных параметров. И @IntRange
, и @FloatRange
наиболее полезны при применении к параметрам, где пользователи могут неправильно определить диапазон.
Аннотация @IntRange
проверяет, находится ли целое или длинное значение параметра в пределах указанного диапазона. В следующем примере показано, что параметр alpha
должен содержать целое значение от 0 до 255:
Котлин
fun setAlpha(@IntRange(from = 0, to = 255) alpha: Int) { ... }
Ява
public void setAlpha(@IntRange(from=0,to=255) int alpha) { ... }
Аннотация @FloatRange
проверяет, находится ли значение параметра с плавающей запятой или двойной точностью в указанном диапазоне значений с плавающей запятой. В следующем примере показано, что параметр alpha
должен содержать значение с плавающей запятой от 0,0 до 1,0:
Котлин
fun setAlpha(@FloatRange(from = 0.0, to = 1.0) alpha: Float) {...}
Ява
public void setAlpha(@FloatRange(from=0.0, to=1.0) float alpha) {...}
Аннотация @Size
проверяет размер коллекции или массива или длину строки. Аннотация @Size
может использоваться для проверки следующих качеств:
- Минимальный размер, например
@Size(min=2)
- Максимальный размер, например
@Size(max=2)
- Точный размер, например
@Size(2)
- Число, которому размер должен быть кратен, например
@Size(multiple=2)
Например, @Size(min=1)
проверяет, не пуста ли коллекция, а @Size(3)
проверяет, содержит ли массив ровно три значения.
Следующий пример показывает, что массив location
должен содержать хотя бы один элемент:
Котлин
fun getLocation(button: View, @Size(min=1) location: IntArray) { button.getLocationOnScreen(location) }
Ява
void getLocation(View button, @Size(min=1) int[] location) { button.getLocationOnScreen(location); }
Аннотации разрешений
Используйте аннотацию @RequiresPermission
для проверки разрешений вызывающего метода. Чтобы проверить наличие одного разрешения из списка допустимых разрешений, используйте атрибут anyOf
. Чтобы проверить набор разрешений, используйте атрибут allOf
. В следующем примере метод setWallpaper()
аннотируется, чтобы указать, что вызывающий метод должен иметь permission.SET_WALLPAPERS
:
Котлин
@RequiresPermission(Manifest.permission.SET_WALLPAPER) @Throws(IOException::class) abstract fun setWallpaper(bitmap: Bitmap)
Ява
@RequiresPermission(Manifest.permission.SET_WALLPAPER) public abstract void setWallpaper(Bitmap bitmap) throws IOException;
В следующем примере требуется, чтобы вызывающая сторона метода copyImageFile()
имела доступ как для чтения к внешнему хранилищу, так и для чтения метаданных местоположения в скопированном изображении:
Котлин
@RequiresPermission(allOf = [ Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.ACCESS_MEDIA_LOCATION ]) fun copyImageFile(dest: String, source: String) { ... }
Ява
@RequiresPermission(allOf = { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.ACCESS_MEDIA_LOCATION}) public static final void copyImageFile(String dest, String source) { //... }
Для разрешений на намерения поместите требование разрешения в строковое поле, которое определяет имя действия намерения:
Котлин
@RequiresPermission(android.Manifest.permission.BLUETOOTH) const val ACTION_REQUEST_DISCOVERABLE = "android.bluetooth.adapter.action.REQUEST_DISCOVERABLE"
Ява
@RequiresPermission(android.Manifest.permission.BLUETOOTH) public static final String ACTION_REQUEST_DISCOVERABLE = "android.bluetooth.adapter.action.REQUEST_DISCOVERABLE";
Для разрешений поставщикам контента, которым требуются отдельные разрешения для доступа к чтению и записи, оберните каждое требование разрешения в аннотацию @RequiresPermission.Read
или @RequiresPermission.Write
:
Котлин
@RequiresPermission.Read(RequiresPermission(READ_HISTORY_BOOKMARKS)) @RequiresPermission.Write(RequiresPermission(WRITE_HISTORY_BOOKMARKS)) val BOOKMARKS_URI = Uri.parse("content://browser/bookmarks")
Ява
@RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS)) @RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS)) public static final Uri BOOKMARKS_URI = Uri.parse("content://browser/bookmarks");
Косвенные разрешения
Если разрешение зависит от конкретного значения, предоставленного параметру метода, используйте @RequiresPermission
для самого параметра без перечисления конкретных разрешений. Например, метод startActivity(Intent)
использует косвенное разрешение на намерение, передаваемое в метод:
Котлин
abstract fun startActivity(@RequiresPermission intent: Intent, bundle: Bundle?)
Ява
public abstract void startActivity(@RequiresPermission Intent intent, @Nullable Bundle)
Когда вы используете косвенные разрешения, инструменты сборки выполняют анализ потока данных, чтобы проверить, имеет ли аргумент, переданный в метод, какие-либо аннотации @RequiresPermission
. Затем они применяют все существующие аннотации параметра самого метода. В примере startActivity(Intent)
аннотации в классе Intent
вызывают появление предупреждений о недопустимом использовании startActivity(Intent)
, когда методу передается намерение без соответствующих разрешений, как показано на рисунке 1.
Инструменты сборки генерируют предупреждение о startActivity(Intent)
из аннотации к соответствующему имени действия намерения в классе Intent
:
Котлин
@RequiresPermission(Manifest.permission.CALL_PHONE) const val ACTION_CALL = "android.intent.action.CALL"
Ява
@RequiresPermission(Manifest.permission.CALL_PHONE) public static final String ACTION_CALL = "android.intent.action.CALL";
При необходимости вы можете заменить @RequiresPermission
на @RequiresPermission.Read
или @RequiresPermission.Write
при аннотировании параметра метода. Однако для косвенных разрешений @RequiresPermission
не следует использовать в сочетании с аннотациями разрешений на чтение или запись.
Аннотации возвращаемого значения
Используйте аннотацию @CheckResult
, чтобы проверить, действительно ли используется результат или возвращаемое значение метода. Вместо аннотации каждого непустого метода с помощью @CheckResult
добавьте аннотацию, чтобы уточнить результаты потенциально запутанных методов.
Например, новые разработчики Java часто ошибочно полагают, что < String >.trim()
удаляет пробелы из исходной строки. Аннотирование метода флагами @CheckResult
использует < String >.trim()
, где вызывающий объект ничего не делает с возвращаемым значением метода.
В следующем примере метод checkPermissions()
аннотируется для проверки того, действительно ли ссылается на возвращаемое значение метода. Он также называет метод enforcePermission()
методом, который следует предложить разработчику в качестве замены:
Котлин
@CheckResult(suggest = "#enforcePermission(String,int,int,String)") abstract fun checkPermission(permission: String, pid: Int, uid: Int): Int
Ява
@CheckResult(suggest="#enforcePermission(String,int,int,String)") public abstract int checkPermission(@NonNull String permission, int pid, int uid);
CallSuper аннотации
Используйте аннотацию @CallSuper
, чтобы убедиться, что переопределяющий метод вызывает суперреализацию метода.
В следующем примере метод onCreate()
аннотируется, чтобы гарантировать, что любые реализации переопределяющего метода вызывают super.onCreate()
:
Котлин
@CallSuper override fun onCreate(savedInstanceState: Bundle?) { }
Ява
@CallSuper protected void onCreate(Bundle savedInstanceState) { }
Аннотации определения типа
Аннотации Typedef проверяют, ссылается ли конкретный параметр, возвращаемое значение или поле на определенный набор констант. Они также позволяют автодополнение кода автоматически предлагать разрешенные константы.
Используйте аннотации @IntDef
и @StringDef
для создания перечисляемых аннотаций наборов целых чисел и строк для проверки других типов ссылок на код.
Аннотации Typedef используют @interface
для объявления нового перечисляемого типа аннотации. Аннотации @IntDef
и @StringDef
вместе с @Retention
аннотируют новую аннотацию и необходимы для определения перечисляемого типа. Аннотация @Retention(RetentionPolicy.SOURCE)
сообщает компилятору не сохранять перечисляемые данные аннотации в файле .class
.
В следующем примере показаны шаги по созданию аннотации, которая проверяет, ссылается ли значение, переданное в качестве параметра метода, на одну из определенных констант:
Котлин
import androidx.annotation.IntDef //... // Define the list of accepted constants and declare the NavigationMode annotation. @Retention(AnnotationRetention.SOURCE) @IntDef(NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS) annotation class NavigationMode // Declare the constants. const val NAVIGATION_MODE_STANDARD = 0 const val NAVIGATION_MODE_LIST = 1 const val NAVIGATION_MODE_TABS = 2 abstract class ActionBar { // Decorate the target methods with the annotation. // Attach the annotation. @get:NavigationMode @setparam:NavigationMode abstract var navigationMode: Int }
Ява
import androidx.annotation.IntDef; //... public abstract class ActionBar { //... // Define the list of accepted constants and declare the NavigationMode annotation. @Retention(RetentionPolicy.SOURCE) @IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS}) public @interface NavigationMode {} // Declare the constants. public static final int NAVIGATION_MODE_STANDARD = 0; public static final int NAVIGATION_MODE_LIST = 1; public static final int NAVIGATION_MODE_TABS = 2; // Decorate the target methods with the annotation. @NavigationMode public abstract int getNavigationMode(); // Attach the annotation. public abstract void setNavigationMode(@NavigationMode int mode); }
При сборке этого кода генерируется предупреждение, если параметр mode
не ссылается ни на одну из определенных констант ( NAVIGATION_MODE_STANDARD
, NAVIGATION_MODE_LIST
или NAVIGATION_MODE_TABS
).
Объедините @IntDef
и @IntRange
чтобы указать, что целое число может быть либо заданным набором констант, либо значением в пределах диапазона.
Включить объединение констант с флагами
Если пользователи могут комбинировать разрешенные константы с флагом (например, |
, &
, ^
и т. д.), вы можете определить аннотацию с атрибутом flag
, чтобы проверить, ссылается ли параметр или возвращаемое значение на допустимый шаблон.
В следующем примере создается аннотация DisplayOptions
со списком допустимых констант DISPLAY_
:
Котлин
import androidx.annotation.IntDef ... @IntDef(flag = true, value = [ DISPLAY_USE_LOGO, DISPLAY_SHOW_HOME, DISPLAY_HOME_AS_UP, DISPLAY_SHOW_TITLE, DISPLAY_SHOW_CUSTOM ]) @Retention(AnnotationRetention.SOURCE) annotation class DisplayOptions ...
Ява
import androidx.annotation.IntDef; ... @IntDef(flag=true, value={ DISPLAY_USE_LOGO, DISPLAY_SHOW_HOME, DISPLAY_HOME_AS_UP, DISPLAY_SHOW_TITLE, DISPLAY_SHOW_CUSTOM }) @Retention(RetentionPolicy.SOURCE) public @interface DisplayOptions {} ...
При создании кода с флагом аннотации генерируется предупреждение, если декорированный параметр или возвращаемое значение не ссылаются на допустимый шаблон.
Сохранить аннотацию
Аннотация @Keep
гарантирует, что аннотированный класс или метод не будет удален при минификации кода во время сборки. Эта аннотация обычно добавляется к методам и классам, доступ к которым осуществляется посредством отражения, чтобы компилятор не считал код неиспользуемым.
Внимание: классы и методы, которые вы аннотируете с помощью @Keep
всегда появляются в APK вашего приложения, даже если вы никогда не ссылаетесь на эти классы и методы в логике вашего приложения.
Чтобы размер вашего приложения был небольшим, подумайте, необходимо ли сохранять в нем каждую аннотацию @Keep
. Если вы используете отражение для доступа к аннотированному классу или методу, используйте условие -if
в правилах ProGuard, указав класс, который выполняет вызовы отражения.
Дополнительные сведения о том, как минимизировать код и указать, какой код не следует удалять, см. в разделе Сжатие, запутывание и оптимизация приложения .
Аннотации видимости кода
Используйте следующие аннотации, чтобы обозначить видимость определенных частей кода, таких как методы, классы, поля или пакеты.
Сделайте код видимым для тестирования
Аннотация @VisibleForTesting
указывает, что аннотированный метод более нагляден, чем обычно необходимо, чтобы его можно было тестировать. Эта аннотация имеет необязательный аргумент otherwise
, который позволяет указать, какой была бы видимость метода, если бы не необходимость сделать его видимым для тестирования. Lint использует аргумент otherwise
для обеспечения желаемой видимости.
В следующем примере myMethod()
обычно является private
, но для тестов он является package-private
. С обозначением VisibleForTesting.PRIVATE
lint отображает сообщение, если этот метод вызывается вне контекста, разрешенного private
доступом, например, из другого модуля компиляции.
Котлин
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) fun myMethod() { ... }
Ява
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) void myMethod() { ... }
Вы также можете указать @VisibleForTesting(otherwise = VisibleForTesting.NONE)
чтобы указать, что метод существует только для тестирования. Эта форма аналогична использованию @RestrictTo(TESTS)
. Они оба выполняют одну и ту же проверку на наличие ворса.
Ограничить API
Аннотация @RestrictTo
указывает, что доступ к аннотированному API (пакету, классу или методу) ограничен следующим образом:
Подклассы
Используйте форму аннотации @RestrictTo(RestrictTo.Scope.SUBCLASSES)
чтобы ограничить доступ API только к подклассам.
Только классы, расширяющие аннотированный класс, могут получить доступ к этому API. Модификатор Java protected
не является достаточно ограничительным, поскольку он разрешает доступ из несвязанных классов в одном пакете. Кроме того, бывают случаи, когда вы хотите оставить метод public
для будущей гибкости, поскольку вы никогда не сможете сделать ранее protected
и переопределенный метод public
, но вы хотите дать подсказку о том, что класс предназначен для использования внутри класса или из подклассов. только.
Библиотеки
Используйте форму аннотации @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
чтобы ограничить доступ API только к вашим библиотекам.
Только код вашей библиотеки может получить доступ к аннотированному API. Это позволяет вам не только организовать свой код в любой иерархии пакетов, которую вы хотите, но и поделиться кодом с группой связанных библиотек. Эта опция уже доступна для библиотек Jetpack, которые имеют много кода реализации, не предназначенного для внешнего использования, но который должен быть public
, чтобы его можно было использовать в различных дополнительных библиотеках Jetpack.
Тестирование
Используйте форму аннотации @RestrictTo(RestrictTo.Scope.TESTS)
чтобы запретить другим разработчикам доступ к вашим API тестирования.
Только тестовый код может получить доступ к аннотированному API. Это не позволяет другим разработчикам использовать API-интерфейсы для разработки, которые вы собираетесь использовать только в целях тестирования.