When an instrumentation test is started, its target package is
restarted with instrumentation code injected and initiated for execution. One
exception is that the target package here can't be the Android application
framework itself, such as the package android
, because doing so leads to the
paradoxical situation where Android framework would need to be restarted, which
is what supports the system functions, including the instrumentation itself.
This means that an instrumentation test cannot inject itself into Android
framework, a.k.a. the system server, for execution. In order to test the Android
framework, the test code can invoke only public API surfaces, or those exposed
using Android Interface Definition Language
AIDL
available in the platform source tree. For this category of tests, it's not meaningful to target any particular package. Therefore, it's customary for such
instrumentations to be declared to target its own test application package, as
defined in its own <manifest>
tag of AndroidManifest.xml
.
Depending on the requirements, test application packages in this category may also:
- Bundle activities needed for testing.
- Share the user ID with the system.
- Be signed with the platform key.
- Be compiled against the framework source rather than the public SDK.
This category of instrumentation tests is sometimes referred to as self-instrumentation. Here are some examples of self-instrumentation tests in the platform source:
The example covered here is writing a new instrumentation test with target package set at its own test application package. This guide uses the following test to serve as an example:
It's recommended to browse through the code first to get a rough impression before proceeding.
Decide on a source location
Typically your team will already have an established pattern of places to check in code, and places to add tests. Most teams own a single git repository, or share one with other teams but have a dedicated sub directory that contains component source code.
Assuming the root location for your component source is at <component source
root>
, most components have src
and tests
folders under it, and some
additional files such as Android.mk
(or broken up into additional .mk
files),
the manifest file AndroidManifest.xml
, and the test configuration file
'AndroidTest.xml'.
Since you are adding a brand new test, you'll probably need to create the
tests
directory next to your component src
, and populate it with content.
In some cases, your team might have further directory structures under tests
due to the need to package different suites of tests into individual apks. And
in this case, you'll need to create a new sub directory under tests
.
Regardless of the structure, you'll end up populating the tests
directory or
the newly created sub directory with files similar to what's in
instrumentation
directory in the sample gerrit change. The details of each
file are explained later in this document.
Manifest file
As with an app project, each instrumentation test module requires a
manifest file called AndroidManifest.xml
. To automatically include
this file using theBUILD_PACKAGE
core makefile, provide this file next to the
Android.mk
file for your test module.
If you aren't familiar with the AndroidManifest.xml
file, refer to the
App Manifest Overview
Following is a sample AndroidManifest.xml
file:
<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>
Some select remarks on the manifest file:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.test.example.helloworld" >
The package
attribute is the application package name: this is the unique
identifier that the Android application framework uses to identify an
application (or in this context: your test application). Each user in the system
can only install one application with that package name.
Furthermore, this package
attribute is the same as what
ComponentName#getPackageName()
returns, and also the same you would use to interact with various pm
sub
commands use adb shell
.
Note that although the package name is typically in the same style as a Java package name, it actually has very few things to do with it. In other words, your application (or test) package may contain classes with any package names, though on the other hand, you could opt for simplicity and have your top level Java package name in your application or test identical to the application package name.
android:sharedUserId="android.uid.system"
This declares that at installation time, this APK file should be granted the
same user ID, i.e. runtime identity, as the core platform. Note that this is
dependent on the apk being signed with same certificate as the core platform
(see LOCAL_CERTIFICATE
in a previous section), yet they are different
concepts:
- some permissions or APIs are signature protected, which requires same signing certificate
- some permissions or APIs requires the
system
user identity of the caller, which requires the calling package to share user ID withsystem
, if it's a separate package from core platform itself
<uses-library android:name="android.test.runner" />
This is required for all Instrumentation tests since the related classes are packaged in a separate framework JAR library file, therefore requires additional classpath entries when the test package is invoked by application framework.
android:targetPackage="android.test.example.helloworld"
You might have noticed that the targetPackage
here is declared the same as the
package
attribute declared in the manifest
tag of this file. As mentioned in
testing basics, this category of instrumentation test are
typically intended for testing framework APIs, so it's not very meaningful for
them to have a specific targeted application package, other then itself.
Simple configuration file
Each new test module must have a configuration file to direct the build system with module metadata, compile-time dependencies and packaging instructions. In most cases, the Soong-based, Blueprint file option is sufficient. For details, see Simple Test Configuration.
Complex configuration file
For these more complex cases, you also need to write a test configuration file for Android's test harness, Trade Federation.
The test configuration can specify special device setup options and default arguments to supply the test class. See the example at /platform_testing/tests/example/instrumentation/AndroidTest.xml.
A snapshot is included here for convenience:
<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>
Some select remarks on the test configuration file:
<target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
<option name="test-file-name" value="HelloWorldTests.apk"/>
</target_preparer>
This tells Trade Federation to install the HelloWorldTests.apk onto the target device using a specified target_preparer. There are many target preparers available to developers in Trade Federation and these can be used to ensure the device is setup properly prior to test execution.
<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>
This specifies the Trade Federation test class to use to execute the test and passes in the package on the device to be executed and the test runner framework which is JUnit in this case.
For more information, see Test Module Configs.
JUnit4 features
Using android-support-test
library as test runner enables adoption of new
JUnit4 style test classes, and the sample gerrit change contains some very basic
use of its features. See the example at
/platform_testing/tests/example/instrumentation/src/android/test/example/helloworld/HelloWorldTest.java.
While testing patterns are usually specific to component teams, there are some generally useful usage patterns.
@RunWith(JUnit4.class)
public class HelloWorldTest {
A significant difference in JUnit4 is that tests are no longer required to inherit from a common base test class; instead, you write tests in plain Java classes and use annotation to indicate certain test setup and constraints. In this example, we are instructing that this class should be run as a JUnit4 test.
@BeforeClass
public static void beforeClass() {
...
@AfterClass
public static void afterClass() {
...
@Before
public void before() {
...
@After
public void after() {
...
@Test
@SmallTest
public void testHelloWorld() {
...
The @Before
and @After
annotations are used on methods by JUnit4 to perform
pre test setup and post test teardown. Similarly, the @BeforeClass
and
@AfterClass
annotations are used on methods by JUnit4 to perform setup before
executing all tests in a test class, and teardown afterwards. Note that the
class-scope setup and teardown methods must be static. As for the test methods,
unlike in earlier version of JUnit, they no longer need to start the method name
with test
, instead, each of them must be annotated with @Test
. As usual,
test methods must be public, declare no return value, take no parameters, and
may throw exceptions.
Instrumentation class access
Although not covered in the basic hello world example, it's fairly common for an
Android test to require access Instrumentation
instance: this is the core API
interface that provides access to application contexts, activity lifecycle
related test APIs and more.
Because the JUnit4 tests no longer require a common base class, it's no longer
necessary to obtain Instrumentation
instance via
InstrumentationTestCase#getInstrumentation()
, instead, the new test runner
manages it via InstrumentationRegistry
where contextual and environmental setup created by instrumentation framework is
stored.
To access the instance of Instrumentation
class, simply call static method
getInstrumentation()
on InstrumentationRegistry
class:
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation()
Build and test locally
For the most common use cases, employ Atest.
For more complex cases requiring heavier customization, follow the instrumentation instructions.