Пример самоинструментирующих тестов

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

Прежде чем продолжить, настоятельно рекомендуется сначала просмотреть обзор манифеста приложения .

Это дает обзор основных компонентов файла манифеста и их функций. См. пример в platform_testing/tests/example/instrumentation/AndroidManifest.xml .

Снимок включен здесь для удобства:

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

    <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" />

Это требуется для всех тестов Instrumentation, поскольку связанные классы упакованы в отдельный файл библиотеки 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>

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

Важно : сами методы тестирования помечены аннотацией @Test ; и обратите внимание, что для выполнения тестов через APCT они должны быть аннотированы размерами тестов: в примере метод testHelloWorld аннотирован как @SmallTest . Аннотация может применяться в области действия метода или области действия класса.

Доступ к instrumentation

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

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

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

Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation()

Создавайте и тестируйте локально

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

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