8.5 KiB
Adding a New Native Test: A Complete Example
[TOC]
If you are new to Android platform development, you might find this complete example of adding a brand new native test from scratch useful to demonstrate the typical workflow involved.
Note that this guide assumes that you already have some knowledge in the platform source tree workflow. If not, please refer to https://source.android.com/source/requirements.
In addition, if you are also unfamiliar with the gtest framework for C++, please check out its project page first:
This guide uses the follow test to serve as an sample:
It's recommended to browse through the code first to get a rough impression before proceeding.
Deciding 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 team owns 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).
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 binaries.
And in this case, you'll need to create a new sub directory under tests
.
To illustrate, here's a typical directory outline for components with a single
tests
folder:
\
<component source root>
\-- Android.mk (component makefile)
\-- AndroidTest.mk (test config file)
\-- src (component source)
| \-- foo.cpp
| \-- ...
\-- tests (test source root)
\-- Android.mk (test makefile)
\-- src (test source)
\-- foo_test.cpp
\-- ...
and here's a typical directory outline for components with multiple test source directories:
\
<component source root>
\-- Android.mk (component makefile)
\-- AndroidTest.mk (test config file)
\-- src (component source)
| \-- foo.cpp
| \-- ...
\-- tests (test source root)
\-- Android.mk (test makefile)
\-- testFoo (sub test source root)
| \-- Android.mk (sub test makefile)
| \-- src (sub test source)
| \-- test_foo.cpp
| \-- ...
\-- testBar
| \-- Android.mk
| \-- src
| \-- test_bar.cpp
| \-- ...
\-- ...
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 native
directory in the sample gerrit change. The sections below will explain in
further details of each file.
Makefile
Each new test module must have a makefile to direct the build system with module metadata, compile time dependencies and packaging instructions.
Latest version of the makefile
A snapshot is included here for convenience:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := \
HelloWorldTest.cpp
LOCAL_MODULE := hello_world_test
LOCAL_MODULE_TAGS := tests
LOCAL_COMPATIBILITY_SUITE := device-tests
include $(BUILD_NATIVE_TEST)
Some select remarks on the makefile:
LOCAL_MODULE := hello_world_test
This setting declares the module name, which must be unique in the entire build
tree. It will also be used as the name as the binary executable of your test, as
well as a make target name, so that you can use make [options] <LOCAL_MODULE>
to build your test binary and all its dependencies.
LOCAL_MODULE_TAGS := tests
This setting declares the module as a test module, which will instruct the build system to generate the native test binaries under "data" output directory, so that they can be packaged into test artifact file bundle.
LOCAL_COMPATIBILITY_SUITE := device-tests
This line builds the testcase as part of the device-tests suite, which is meant to target a specific device and not a general ABI.
include $(BUILD_NATIVE_TEST)
This includes a core makefile in build system that performs the necessary steps
to compile your test, together with gtest framework under external/gtest
, into
a native test binary. The generated binary will have the same name as
LOCAL_MODULE
. And if tests
is used as LOCAL_MODULE_TAGS
and there are no
other customizations, you should be able to find your test binary in:
${OUT}/data/nativetest[64]/<LOCAL_MODULE>/<LOCAL_MODULE>
e.g. ${OUT}/data/nativetest[64]/hello_world_test/hello_world_test
And you will also find it:
- ${OUT}/target/product//testcases/<LOCAL_MODULE>//<LOCAL_MODULE>
e.g. ${OUT}/target/product/arm64-generic/testcases/hello_world_test/arm/hello_world_test & ${OUT}/target/product/arm64-generic/testcases/hello_world_test/arm64/hello_world_test
Note: if the native ABI type of device is 64bit, such as angler, bullhead etc,
the directory name will be suffixed with 64
.
Please also note that currently the native tests in APCT does not support use of dynamically linked libraries, which means that the dependencies needs to be statically linked into the test binary.
Source code
Annotated source code is listed below:
#include <gtest/gtest.h>
Header file include for gtest. Note that the include file dependency is
automatically resolved by using BUILD_NATIVE_TEST
in the makefile
#include <stdio.h>
TEST(HelloWorldTest, PrintHelloWorld) {
printf("Hello, World!");
}
gtests are written by using TEST
macro: the first parameter is the test case
name, and the second is test name; together with test binary name, they form the
hierarchy below when visualized in result dashboard:
<test binary 1>
| \-- <test case 1>
| | \-- <test 1>
| | \-- <test 2>
| | \-- ...
| \-- <test case 2>
| | \-- <test 1>
| | \-- ...
| \-- ...
<test binary 2>
|
...
For more information on writing tests with gtest, see its documentation:
Test Config
In order to simplify test execution, you also need write a test configuration file for Android's test harness, TradeFederation.
The test configuration can specify special device setup options and default arguments to supply the test class.
A snapshot is included here for convenience:
<configuration description="Config for APCT native hello world test cases">
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
<option name="cleanup" value="true" />
<option name="push" value="hello_world_test->/data/local/tmp/hello_world_test" />
</target_preparer>
<test class="com.android.tradefed.testtype.GTest" >
<option name="native-test-device-path" value="/data/local/tmp" />
<option name="module-name" value="hello_world_test" />
<option name="runtime-hint" value="8m" />
</test>
</configuration>
Some select remarks on the test configuration file:
<target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
<option name="cleanup" value="true" />
<option name="push" value="hello_world_test->/data/local/tmp/hello_world_test" />
</target_preparer>
This tells TradeFederation to install the hello_world_test binary onto the target device using a specified target_preparer. There are many target preparers available to developers in TradeFederation and these can be used to ensure the device is setup properly prior to test execution.
<test class="com.android.tradefed.testtype.GTest" >
<option name="native-test-device-path" value="/data/local/tmp" />
<option name="module-name" value="hello_world_test" />
<option name="runtime-hint" value="8m" />
</test>
This specifies the TradeFederation test class to use to execute the test and passes in the native test location that it was installed.
Look here for more information on Test Module Configs
Build & Test Locally
Follow these Instructions to build and execute your test