按照此快速入门指南,根据 简单的单元测试代码示例编写驱动程序单元测试:
添加库依赖项
在 C++ 文件中添加此库依赖项以及 gtest 依赖项:
#include <lib/driver/testing/cpp/driver_test.h>
#include <gtest/gtest.h>
您还需要将相应目标添加到 BUILD.gn 文件的 deps 中:
deps = [
"//sdk/lib/driver/testing/cpp",
"//src/lib/fxl/test:gtest_main",
]
该库提供了两个可在测试中使用的类。
在示例中,我们使用了 ForegroundDriverTest,但也有 BackgroundDriverTest 可用。
如需了解详情,请参阅下文的前台与后台。
创建配置类
测试定义了一个配置类,以通过模板形参传递到库中。 该类负责管理单元测试的各个部分,确保它们在正确的调度器上下文中运行。
此配置类必须定义两种类型,一种用于驱动程序,另一种用于驱动程序的环境依赖项,我们将在下一部分中介绍。
以下是示例中的配置类示例:
class TestConfig final {
public:
using DriverType = simple::SimpleDriver;
using EnvironmentType = SimpleDriverTestEnvironment;
};
定义环境类型类
EnvironmentType 必须是一个隔离类,用于提供驱动程序的自定义依赖项。它不需要提供框架依赖项(compat::DeviceServer 除外),因为该库已经提供了这些依赖项。
如果不需要额外的依赖项,请使用 fdf_testing::MinimalCompatEnvironment,它提供默认的 compat::DeviceServer(请注意,这仅在树内可用,因为 compat 协议不在 SDK 中)。
以下是示例中的环境:
class SimpleDriverTestEnvironment : public fdf_testing::Environment {
public:
zx::result<> Serve(fdf::OutgoingDirectory& to_driver_vfs) override {
// Perform any additional initialization here, such as setting up compat device servers
// and FIDL servers.
return zx::ok();
}
};
定义测试
现在,我们可以将所有内容整合在一起并进行测试。在我们的示例中,它看起来如下所示:
class SimpleDriverTest : public ::testing::Test {
public:
void SetUp() override {
zx::result<> result = driver_test().StartDriver();
ASSERT_EQ(ZX_OK, result.status_value());
}
void TearDown() override {
zx::result<> result = driver_test().StopDriver();
ASSERT_EQ(ZX_OK, result.status_value());
}
fdf_testing::ForegroundDriverTest<TestConfig>& driver_test() {
return driver_test_;
}
private:
fdf_testing::ForegroundDriverTest<TestConfig> driver_test_;
};
TEST_F(SimpleDriverTest, VerifyChildNode) {
driver_test().RunInNodeContext([](fdf_testing::TestNode& node) {
EXPECT_EQ(1u, node.children().size());
EXPECT_TRUE(node.children().count("simple_child"));
});
}
运行单元测试
驱动程序单元测试在驱动程序本身的测试文件夹内执行。 例如,执行以下命令来运行 iwlwifi 驱动程序的驱动程序测试:
tools/bazel test third_party/iwlwifi/test:iwlwifi_test_pkg配置实参
DriverType
被测驱动程序的类型,将通过 driver() 和 RunInDriverContext() 函数提供。
默认情况下,这不用于驱动程序生命周期管理(即启动/停止驱动程序)。 这是通过驱动程序中的 FUCHSIA_DRIVER_EXPORT 宏调用创建的驱动程序注册符号实现的。
如果在 DriverType 中使用自定义的特定于测试的驱动程序(例如,提供特定于测试的函数),请添加静态 GetDriverRegistration 函数,如下所示。这将替换全局注册符号。
static DriverRegistration GetDriverRegistration()
EnvironmentType
一个类,其中包含被测驱动程序的自定义依赖项。 环境将始终位于后台调度器上。
它必须是默认可构造的,派生自 fdf_testing::Environment class,
并替换以下函数:
zx::result<> Serve(fdf::OutgoingDirectory& to_driver_vfs) override;
在启动被测驱动程序时,系统会在后台环境调度器上自动调用该函数。
它必须将其各个部分添加到提供的 fdf::OutgoingDirectory object 中,通常通过 AddService 方法完成。
OutgoingDirectory 支持驱动程序的传入命名空间,因此其名称为 to_driver_vfs。
以下是提供 compat 协议和自定义测试定义的 FIDL 服务器的自定义环境示例:
class MyFidlServer : public fidl::WireServer<fuchsia_examples_gizmo::Proto> {...};
class CustomEnvironment : public fdf_testing::Environment {
public:
zx::result<> Serve(fdf::OutgoingDirectory& to_driver_vfs) {
device_server_.Initialize(component::kDefaultInstance);
EXPECT_EQ(ZX_OK, device_server_.Serve(
fdf::Dispatcher::GetCurrent()->async_dispatcher(), &to_driver_vfs));
EXPECT_EQ(ZX_OK, to_driver_vfs.AddService<fuchsia_examples_gizmo::Service::Proto>(
custom_server_.CreateInstanceHandler()).status_value());
return zx::ok();
}
private:
compat::DeviceServer device_server_;
MyFidlServer custom_server_;
};
前台与后台
前台和后台驱动程序测试之间的选择取决于测试计划如何与被测驱动程序通信。如果测试将大量调用驱动程序上的公共方法,则应选择前台驱动程序测试。如果测试将更频繁地通过驱动程序公开的 FIDL 进行调用,则应选择后台驱动程序测试。
使用前台版本时,测试可以使用 driver() 方法访问被测驱动程序并直接对其进行调用,但发送到驱动程序提供的 FIDL 的同步客户端任务必须通过 RunOnBackgroundDispatcherSync()。
使用后台版本时,测试可以对驱动程序提供的 FIDL 进行同步 FIDL 调用,但在访问驱动程序实例时必须通过 RunInDriverContext()。
driver_test()
如上面的测试示例所示,测试创建了一个 driver_test() getter,用于返回对库类的引用。此对象提供了测试可用于对其测试执行各种操作的所有控件,例如启动驱动程序、连接到驱动程序和运行任务。
有些方法在后台和前台测试中都可用,有些方法则特定于线程模式。如需了解可用方法,请参阅下文。
前台测试中可用的方法
driver
这可用于直接从测试访问驱动程序。 由于驱动程序位于前台,因此可以在主测试线程上安全地访问此驱动程序。
RunOnBackgroundDispatcherSync
在与驱动程序分开的后台调度器上运行任务。 这样做是为了避免在对位于前台的驱动程序进行同步客户端调用时与驱动程序发生死锁。
后台测试中可用的方法
RunInDriverContext
这可用于在被测驱动程序上运行回调。 回调输入将引用驱动程序。 对驱动程序的所有访问都必须通过此方法,因为在驱动程序位于后台时,在主测试线程上访问驱动程序是不安全的。
两种测试中都可用的方法
runtime
访问驱动程序运行时对象。 这可用于创建新的后台调度器或运行前台调度器。 用户无需为环境或驱动程序显式创建调度器,因为该库会负责处理此问题。
StartDriver
这可用于启动被测驱动程序。等待启动完成,然后返回结果。
StartDriverWithCustomStartArgs
与 StartDriver 相同,但可以在将驱动程序启动实参发送到驱动程序之前对其进行修改。
StopDriver
停止被测驱动程序。这会在 DriverBase 实现上调用 PrepareStop 并等待完成。如果 StartDriver 成功,则必须调用此方法。如果 StartDriver 失败,也可以调用此方法,但为了与驱动程序主机的行为保持一致,它将是一个空操作。
ShutdownAndDestroyDriver
关闭属于被测驱动程序的驱动程序调度器,然后调用驱动程序的销毁钩子。这会在测试销毁 时自动发生,但如果测试需要提前 进行某些验证或 重新启动驱动程序,也可以手动调用此方法
Connect
连接到被测驱动程序提供的服务成员实例。 这可以是基于驱动程序传输或 Zircon 通道传输的服务。
ConnectThroughDevfs
连接到驱动程序通过 devfs 导出的协议。
这可以给定 devfs 节点的 node_name,也可以给定在到达 devfs 节点之前要遍历的节点名称列表。
RunInEnvironmentTypeContext
在测试正在使用的 EnvironmentType 实例上运行任务。
RunInNodeContext
在测试正在使用的 fdf_testing::TestNode 实例上运行任务。
这可用于验证驱动程序与驱动程序框架节点的交互(例如检查已添加的子项数量)。
在单个测试中多次启动驱动程序
如需在测试中多次启动/停止驱动程序而不更改 环境,请务必完成所有 3 个步骤: - StartDriver/StartDriverWithCustomStartArgs - StopDriver - ShutdownAndDestroyDriver
Run* 函数警告
使用 Run* 函数(RunInDriverContext、RunOnBackgroundDispatcherSync、RunInEnvironmentTypeContext、RunInNodeContext)时要小心。这些任务在特定调度器上运行,因此执行以下操作可能不安全:
- 从另一个上下文(主线程或其他 Run* 类型)将原始指针传递到函数中以在函数中使用
- 从函数中返回原始指针(通过捕获的引用或返回类型)以在主线程上使用,或在另一个 Run* 函数中捕获/使用(同一类型的 Run* 函数除外)。