23、AOSP中的测试构建:测试模块定义、测试框架集成、测试编译与运行
说到AOSP的测试构建,我其实挺有感触的。刚入行那会儿,我总觉得测试是“别人的事”——代码写完能跑起来不就行了?直到有一次,我提交了一个修改,结果把整个系统的音频路由搞崩了。嗯,从那以后,我再也不敢轻视测试构建了。
说白了,AOSP的测试体系就是一套“自检机制”。它让你在提交代码之前,先自己把雷踩一遍。今天我们就来聊聊,怎么在AOSP里定义测试模块、集成测试框架,以及如何编译和运行这些测试。
测试模块的定义
在AOSP里,每个测试都是一个独立的模块。你需要在Android.bp或Android.mk里把它“注册”进去。我个人习惯用Android.bp,因为语法更简洁,而且Soong构建系统对它的支持更好。
来看一个最简单的例子。假设我们要测试一个叫libfoo的库,可以这样写:
// Android.bp
cc_test {
name: "libfoo_test",
srcs: ["foo_test.cpp"],
shared_libs: ["libfoo"],
test_suites: ["device-tests"],
}
这里有几个关键点:
- name:测试模块的名字,全局唯一。我习惯在模块名后面加个
_test后缀,一眼就能看出来是测试。 - srcs:测试源文件。可以是C++、Java或者Kotlin,取决于你用什么框架。
- shared_libs:依赖的共享库。这里链接了
libfoo,这样测试代码就能调用它的接口了。 - test_suites:指定测试套件。常见的值有
device-tests(设备端测试)和host-tests(主机端测试)。
java_test而不是cc_test。我见过有人把Java测试写成cc_test,结果编译报错半天找不到原因。
测试框架的集成
AOSP支持多种测试框架。最常用的是Google Test(C++)和JUnit(Java/Kotlin)。另外还有TradeFed,它是AOSP的自动化测试框架,负责测试的调度和报告生成。
我个人最常用的是Google Test。它的集成方式很简单,在Android.bp里加上gtest依赖就行:
cc_test {
name: "libfoo_test",
srcs: ["foo_test.cpp"],
shared_libs: ["libfoo"],
static_libs: ["libgtest", "libgmock"],
test_suites: ["device-tests"],
}
然后你的测试代码就可以这样写:
#include <gtest/gtest.h>
#include "foo.h"
TEST(FooTest, AddWorks) {
Foo foo;
EXPECT_EQ(foo.add(1, 2), 3);
}
TEST(FooTest, SubtractWorks) {
Foo foo;
EXPECT_EQ(foo.subtract(5, 3), 2);
}
为什么我要强调libgmock?因为在实际项目中,你经常需要mock一些依赖。比如测试网络模块时,你不能真的去连外网,这时候mock就派上用场了。我曾经在一个项目里,因为没有用mock,测试跑一次要等30秒——后来改成mock,3秒就跑完了。
AndroidTest.xml里定义测试的元数据,比如测试名称、超时时间、需要的权限等。这个文件通常放在测试模块的根目录下。
测试的编译
编译测试模块和编译普通模块没什么两样。你只需要指定模块名就行:
source build/envsetup.sh
lunch aosp_arm64-eng
mma libfoo_test
mma是“make module alone”的缩写,只编译指定的模块。如果你改了依赖库,记得先编译依赖:
mma libfoo
mma libfoo_test
这里有个坑:如果你用mm(不带a),它只会编译当前目录下的模块。而mma会递归编译所有依赖。我刚开始用的时候,经常因为用了mm导致链接错误,后来就只用mma了。
编译完成后,生成的可执行文件在out/target/product/<device>/test/目录下。你可以直接push到设备上运行:
adb push out/target/product/generic/test/libfoo_test /data/local/tmp/
adb shell /data/local/tmp/libfoo_test
Android.bp里加上required: ["root"],否则测试会直接失败。
测试的运行
运行测试的方式有很多种。最简单的就是直接adb shell执行。但如果你要跑整个测试套件,我建议用TradeFed:
atest libfoo_test
atest是AOSP的测试运行工具。它会自动编译、推送、运行测试,并生成报告。我个人非常喜欢这个工具,因为它省去了手动操作的麻烦。
如果你只想跑某个特定的测试用例,可以用--test-filter参数:
atest libfoo_test --test-filter "FooTest.AddWorks"
这样只会运行AddWorks这一个用例。调试的时候特别有用。
另外,atest还支持并行运行。你可以指定同时跑多少个测试:
atest libfoo_test --parallel 4
我一般设成CPU核心数的一半。设太多反而会因为资源竞争变慢。
测试构建的整体流程
为了让你更直观地理解,我画了一张流程图:
这张图把整个流程串起来了。你想想看,从定义模块到运行测试,其实就四步。但每一步都有细节要注意。比如定义模块时,test_suites写错了,测试可能不会被自动发现。集成框架时,依赖没加全,编译就会报错。
避坑指南
最后,分享几个我踩过的坑:
- 测试超时:有些测试跑得慢,默认超时时间可能不够。在
AndroidTest.xml里加上timeout="5m",把时间设长一点。 - 权限问题:测试需要读写文件?记得在
Android.bp里声明权限。我遇到过测试跑一半崩溃,结果是因为没给存储权限。 - 依赖冲突:如果测试依赖了多个版本的同一个库,链接时会报错。用
static_libs代替shared_libs可以避免这个问题。
嗯,关于AOSP的测试构建,今天就聊这么多。测试这件事,说白了就是“早发现、早解决”。你花10分钟写个测试,可能就省下了后面10小时的调试时间。这笔账,怎么算都划算。
公众号:蓝海资料掘金营,微信deep3321