Пример самоинструментируемого теста

Когда начинается инструментальный тест, целевой пакет перезапускается с внедренным и запущенным кодом инструментария. Исключением является то, что целевым пакетом здесь не может быть сам фреймворк приложения для Android, например пакет android, поскольку это приведет к парадоксальной ситуации, когда фреймворк Android, поддерживающий системные функции, включая само инструментирование, должен будет перезапуститься.

Это означает, что инструментальный тест не может внедрить себя в фреймворк Android (системный сервер) для выполнения. Чтобы протестировать фреймворк Android, тестовый код может вызывать только общедоступные API или те, которые доступны через язык описания интерфейсов Android (AIDL) в дереве исходного кода платформы. Для этой категории тестов не имеет смысла выбирать определенный пакет. Поэтому обычно такие инструменты объявляются для целевого пакета тестового приложения, как определено в собственном теге <manifest> файла AndroidManifest.xml.

В зависимости от требований тестовые пакеты приложений этой категории могут также:

  • Действия, необходимые для тестирования.
  • Передавать идентификатор пользователя системе.
  • Подписан ключом платформы.
  • компилироваться на основе исходного кода фреймворка, а не общедоступного SDK;

Такую категорию инструментальных тестов иногда называют самоинструментированием. Вот несколько примеров инструментальных тестов в исходном коде платформы:

В этом примере мы создадим новый инструментальный тест, в котором целевой пакет будет задан в пакете тестового приложения. В этом руководстве в качестве примера используется следующий тест:

Рекомендуем сначала просмотреть код, чтобы получить общее представление о нем.

Выберите исходное местоположение

Как правило, у вашей команды уже есть определенный порядок проверки кода и добавления тестов. У большинства команд есть один репозиторий Git или общий репозиторий с другими командами, но с отдельным подкаталогом, содержащим исходный код компонента.

Если корневой каталог исходного кода компонента находится в папке <component source root>, то большинство компонентов содержат в ней папки src и tests, а также некоторые дополнительные файлы, например Android.mk (или несколько файлов .mk), файл манифеста AndroidManifest.xml и файл конфигурации тестирования AndroidTest.xml.

Поскольку вы добавляете совершенно новый тест, вам, вероятно, потребуется создать каталог tests рядом с компонентом src и заполнить его контентом.

В некоторых случаях в каталоге tests могут быть подкаталоги, если разные наборы тестов нужно упаковать в отдельные APK-файлы. В этом случае вам нужно будет создать новый подкаталог в tests.

Независимо от структуры, вы заполните каталог tests или созданный подкаталог файлами, похожими на те, что находятся в каталоге instrumentation в примере изменений Gerrit. Подробная информация о каждом файле приведена ниже.

Файл манифеста

Как и в случае с проектом приложения, для каждого модуля инструментального теста требуется файл манифеста AndroidManifest.xml. Чтобы автоматически включить этот файл с помощью основного файла makefile BUILD_PACKAGE, поместите его рядом с файлом Android.mk для тестового модуля.

Если вы не знакомы с файлом AndroidManifest.xml, ознакомьтесь со статьей Обзор манифеста приложения.

Ниже приведен пример файла AndroidManifest.xml.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  android:sharedUserId="android.uid.system"
  package="android.test.example.helloworld" >

    <application>
       <uses-library android:name="android.test.runner"/>
    </application>

    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
                     android:targetPackage="android.test.example.helloworld"
                     android:label="Hello World Test"/>

</manifest>

Некоторые примечания к файлу манифеста:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="android.test.example.helloworld" >

Атрибут package – это название пакета приложения, то есть уникальный идентификатор, который фреймворк приложения для Android использует для идентификации приложения (в данном случае – вашего тестового приложения). Каждый пользователь в системе может установить только одно приложение с таким названием пакета.

Кроме того, атрибут package совпадает с атрибутом, который возвращает команда ComponentName#getPackageName(), а также с атрибутом, который используется для взаимодействия с различными вложенными командами pm, использующими adb shell.

Обратите внимание, что, хотя название пакета обычно имеет тот же стиль, что и название пакета Java, на самом деле оно имеет с ним мало общего. Другими словами, пакет приложения (или теста) может содержать классы с любыми названиями пакетов. С другой стороны, вы можете упростить себе задачу и сделать название пакета Java верхнего уровня в приложении или тесте идентичным названию пакета приложения.

android:sharedUserId="android.uid.system"

Это означает, что при установке файлу APK должен быть назначен тот же идентификатор пользователя (идентификатор среды выполнения), что и у основной платформы. Обратите внимание, что это зависит от того, подписан ли APK тем же сертификатом, что и основная платформа (см. LOCAL_CERTIFICATE в предыдущем разделе), но это разные понятия:

  • некоторые разрешения или API защищены подписью, для которой требуется тот же сертификат подписи;
  • Для некоторых разрешений или API требуется system идентификатор пользователя вызывающего приложения. Если вызывающее приложение не является частью основной платформы, оно должно передать идентификатор пользователя в system.
<uses-library android:name="android.test.runner" />

Это необходимо для всех инструментальных тестов, поскольку связанные классы упакованы в отдельный файл библиотеки JAR фреймворка, поэтому при вызове тестового пакета фреймворком приложения требуются дополнительные записи пути к классам.

android:targetPackage="android.test.example.helloworld"

Вы могли заметить, что targetPackage здесь объявлен так же, как и атрибут package, объявленный в теге manifest этого файла. Как упоминалось в разделе основы тестирования, эта категория инструментальных тестов обычно предназначена для тестирования API фреймворка, поэтому для них не имеет особого смысла указывать целевой пакет приложения, кроме самого себя.

Простой файл конфигурации

Каждый новый тестовый модуль должен иметь файл конфигурации, чтобы направлять систему сборки с метаданными модуля, зависимостями времени компиляции и инструкциями по упаковке. В большинстве случаев достаточно варианта с файлом Blueprint на основе Soong. Подробнее о простой конфигурации тестирования…

Сложный файл конфигурации

В более сложных случаях вам также потребуется написать файл конфигурации для тестовой программы Android, Trade Federation.

В конфигурации теста можно указать специальные параметры настройки устройства и аргументы по умолчанию, которые будут передаваться тестовому классу. Пример можно найти в файле /platform_testing/tests/example/instrumentation/AndroidTest.xml.

Для удобства ниже приведен снимок экрана.

<configuration description="Runs sample instrumentation test.">
  <target_preparer class="com.android.tradefed.targetprep.TestFilePushSetup"/>
  <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
    <option name="test-file-name" value="HelloWorldTests.apk"/>
  </target_preparer>
  <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"/>
  <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"/>
  <option name="test-suite-tag" value="apct"/>
  <option name="test-tag" value="SampleInstrumentationTest"/>

  <test class="com.android.tradefed.testtype.AndroidJUnitTest">
    <option name="package" value="android.test.example.helloworld"/>
    <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/>
  </test>
</configuration>

Примечания к файлу конфигурации теста:

<target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
  <option name="test-file-name" value="HelloWorldTests.apk"/>
</target_preparer>

Это указывает Trade Federation установить HelloWorldTests.apk на целевое устройство с помощью указанного target_preparer. В Trade Federation разработчикам доступно множество подготовителей целей, которые можно использовать, чтобы убедиться, что устройство правильно настроено перед выполнением теста.

<test class="com.android.tradefed.testtype.AndroidJUnitTest">
  <option name="package" value="android.test.example.helloworld"/>
  <option name="runner" value="android.support.test.runner.AndroidJUnitRunner"/>
</test>

Здесь указывается класс тестирования Trade Federation, который будет использоваться для выполнения теста, а также передаются пакеты на устройство для выполнения и фреймворк для запуска тестов, в данном случае JUnit.

Подробнее о конфигурациях модулей тестирования…

Функции JUnit4

Использование библиотеки android-support-test в качестве средства запуска тестов позволяет применять новые классы тестов в стиле JUnit4. В образце изменения Gerrit приведены некоторые основные примеры использования функций этой библиотеки. Пример можно найти в файле /platform_testing/tests/example/instrumentation/src/android/test/example/helloworld/HelloWorldTest.java.

Хотя шаблоны тестирования обычно относятся к командам, работающим над определенными компонентами, есть некоторые общие шаблоны использования.

@RunWith(JUnit4.class)
public class HelloWorldTest {

Важное отличие JUnit4 заключается в том, что тесты больше не должны наследовать общий базовый тестовый класс. Вместо этого вы пишете тесты в простых классах Java и используете аннотации для указания определенных настроек и ограничений тестов. В этом примере мы указываем, что этот класс должен запускаться как тест JUnit4.

    @BeforeClass
    public static void beforeClass() {
    ...
    @AfterClass
    public static void afterClass() {
    ...
    @Before
    public void before() {
    ...
    @After
    public void after() {
    ...
    @Test
    @SmallTest
    public void testHelloWorld() {
    ...

Аннотации @Before и @After используются в методах JUnit4 для настройки перед тестом и очистки после него. Аналогично аннотации @BeforeClass и @AfterClass используются в методах JUnit4 для выполнения настройки перед выполнением всех тестов в классе и завершения работы после этого. Обратите внимание, что методы настройки и очистки в области класса должны быть статическими. Что касается методов тестирования, то в отличие от более ранних версий JUnit, их названия больше не должны начинаться с test. Вместо этого каждый из них должен быть аннотирован с помощью @Test. Как обычно, тестовые методы должны быть общедоступными, не возвращать значений, не принимать параметров и могут вызывать исключения.

Доступ к классу Instrumentation

Хотя в базовом примере "Hello, world!" это не рассматривается, в тестах Android часто требуется доступ к экземпляру Instrumentation. Это основной интерфейс API, который предоставляет доступ к контекстам приложений, API тестирования, связанным с жизненным циклом объекта activity, и т. д.

Поскольку тесты JUnit4 больше не требуют общего базового класса, нет необходимости получать экземпляр Instrumentation через InstrumentationTestCase#getInstrumentation(). Вместо этого новый запускатель тестов управляет им через InstrumentationRegistry, где хранится контекстная и экологическая настройка, созданная фреймворком для инструментов.

Чтобы получить доступ к экземпляру класса Instrumentation, просто вызовите статический метод getInstrumentation() для класса InstrumentationRegistry:

Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation()

Создание и тестирование на локальном компьютере

Для большинства распространенных вариантов использования используйте Atest.

Если вам нужно настроить более сложные сценарии, следуйте инструкциям по настройке.