Пример самоинструментационных тестов

Когда запускается инструментальный тест, его целевой пакет перезапускается с внедрением инструментального кода и инициируется для выполнения. Единственным исключением является то, что целевой пакет здесь не может быть самой платформой приложения 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 в образце изменения геррита. Подробности каждого файла описаны далее в этом документе.

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

Как и в случае с проектом приложения, каждому модулю инструментального тестирования требуется файл манифеста AndroidManifest.xml . Чтобы автоматически включить этот файл с помощью основного make-файла 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 . Как обычно, методы тестирования должны быть общедоступными, не объявлять возвращаемого значения, не принимать параметров и могут генерировать исключения.

Доступ к классу инструментов

Хотя это и не рассматривается в базовом примере hello world, для теста Android довольно часто требуется доступ к экземпляру Instrumentation : это основной интерфейс API, который обеспечивает доступ к контекстам приложений, API-интерфейсам тестирования, связанным с жизненным циклом активности, и т. д.

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

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

Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation()

Сборка и тестирование локально

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

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