本指南介绍如何为 Fuchsia 的
bt-host
驱动程序
SMP_Phase1Test
测试夹具和
FeatureExchangeBothSupportSCFeaturesHaveSC
测试用例。
bt-host
驱动程序实现了蓝牙核心规范 v5.2 的大部分功能,
卷 3(主机子系统)。
Fuchsia 项目 注重自动化测试 并且蓝牙团队力争 测试文化领导者。
如需合并 Fuchsia 源代码树中的 bt-host
驱动程序更改,您需要
因此需要针对这项更改编写自动化测试
简介
bt-host
是一个相对较大的代码库,包含许多抽象层。
下面是 bt-host
驱动程序的主要逻辑组件示意图:
{#architecture-diagram}
图中的每个抽象层大致对应于整个协议。
使用 bt-host
测试时,请专注于了解两者之间的关系
当前处理的层(例如 SM)
(例如 L2CAP)。
虽然每一层都可能有额外的内部抽象层, 协议间关系最常被模仿和/或运用 测试。
资源
bt-host
使用 C++ 编写,Fucsia 使用
gUnit
用于 C++ 测试的 Googletest 库。
如需使用 bt-host
单元测试,您需要对
以下资源:
- GTest 入门指南
<ph type="x-smartling-placeholder">
- </ph>
- 测试夹具
在编写 bt-host
测试时偶尔会出现以下主题,
可以根据需要引用:
bt-host
单元测试概览
大多数 bt-host
测试都使用以下模式编写:
在该测试夹具中构建 LUT,使用测试替身代替 LUT 的依赖关系。
在 LUT 中练习功能。例如,本指南探讨的是
FeatureExchangeBothSupportSCFeaturesHaveSC
测试用例。验证更高级别的命令是否会导致预期的行为。
测试夹具
GTest 测试夹具是可重复使用的环境,通常存储测试
依赖项,并为编写单元测试提供便捷方法。测试
本例中使用的设备是 SMP_Phase1Test
类。
测试双打
测试替身是用于在测试代码中替换真实对象的对象。那里
是许多不同类型的测试替身。这里展示的两个测试替身示例
SMP_Phase1Test
为FakeChannel
和
FakeListener
。
SMP_Phase1Test
测试夹具
SMP_Phase1Test
是用于测试
sm::Phase1
类。
Phase1
类负责蓝牙低功耗 (BLE) 的第 1 阶段
配对,在此过程中,设备会协商配对的安全功能。
本部分将 SMP_Phase1Test
的测试固件设置代码标注为
典型的
“创建测试夹具”和“构建 LUT”。
如果您能
phase_1_unittest.cc
打开它
SetUp()
、TearDown()
和 NewPhase1()
方法
SMP_Phase1Test
的构造函数不会执行任何操作。bt-host
会测试
固件通常使用 GTest SetUp()
方法初始化测试
灯具。
void SetUp() override { NewPhase1(); }
NewPhase1
是一种带有默认参数的 protected
可见性方法。
bt-host
测试固件通常会委托给 New<test-fixture-name>
方法
(带有 SetUp()
中的默认参数)。New
* 方法会执行以下任务
设置资源/测试替身并创建 LUT。这样可启用测试
case1 使用不同的方法调用 New
*,以重新初始化测试固件
参数。
void NewPhase1(Role role = Role::kInitiator,
Phase1Args phase_args = Phase1Args(),
hci::Connection::LinkType ll_type =
hci::Connection::LinkType::kLE) {
对于 NewPhase1
,可配置切面如下:
- 设备角色
- 传输类型
- 包含剩余 Phase1 参数的结构体,使其成为 而只需更改其中一个“默认”参数:
Phase1Args
结构体
struct Phase1Args {
PairingRequestParams preq = PairingRequestParams();
IOCapability io_capability = IOCapability::kNoInputNoOutput;
BondableMode bondable_mode = BondableMode::Bondable;
SecurityLevel level = SecurityLevel::kEncrypted;
bool sc_supported = false;
};
L2CAP 模拟依赖项
L2CAP
通道提供与对等协议/服务的逻辑连接,以及
依赖于 ATT
、GATT
、SMP
、SDP
等更高级别的协议。
FakeChannel
用作模拟依赖项,用于测试真实对象如何发送和
通过以下应用接收邮件:
L2CAP 渠道。
在 NewPhase1
中创建的第一个测试替身是 FakeChannel
模拟对象:
uint16_t mtu =
phase_args.sc_supported ? l2cap::kMaxMTU : kNoSecureConnectionsMtu;
ChannelOptions options(cid, mtu);
options.link_type = ll_type;
...
fake_chan_ = CreateFakeChannel(options);
sm_chan_ = std::make_unique<PairingChannel>(fake_chan_);
CreateFakeChannel
方法可用,因为 SMP_Phase1Test
继承了
来自
FakeChannelTest
。2
FakeListener
在实际代码中,PairingPhase
使用 PairingPhase::Listener
与
较高级别的 SecurityManager
类。FakeListener
对此进行了模拟,
测试依赖项
listener_ = std::make_unique<FakeListener>();
虽然不是协议级依赖项,但 FakeListener
也体现了另一种常见的
bt-host
测试图案。类通常会使用接口指针进行通信
上面是层实现这些接口的测试替身会传递到
验证 LUT 是否与上层正确通信。
完成回调
Phase1
存储一个回调参数。Phase1
完成后,它会返回
Phase1
的结果。complete_cb
用作此项
在实例化 Phase1
时调用回调。
complete_cb
会存储 Phase1
的结果(在本例中为 features
,
preq
和 pres
参数)传递到测试固件变量(features_
、
last_pairing_req_
和 last_pairing_res_
),以便测试用例可以检查
正确生成这些变量。
auto complete_cb = [this](PairingFeatures features,
PairingRequestParams preq,
PairingResponseParams pres) {
feature_exchange_count_++;
features_ = features;
last_pairing_req_ = util::NewPdu(sizeof(PairingRequestParams));
last_pairing_res_ = util::NewPdu(sizeof(PairingResponseParams));
PacketWriter preq_writer(kPairingRequest, last_pairing_req_.get());
PacketWriter pres_writer(kPairingResponse, last_pairing_res_.get());
*preq_writer.mutable_payload<PairingRequestParams>() = preq;
*pres_writer.mutable_payload<PairingResponseParams>() = pres;
};
LUT 实例化
下一步是根据 NewPhase1
创建 Phase1
LUT
参数。LUT 存储在测试固件的 phase_1_
变量中。
if (role == Role::kInitiator) {
phase_1_ = Phase1::CreatePhase1Initiator(
sm_chan_->GetWeakPtr(), listener_->as_weak_ptr(),
phase_args.io_capability, phase_args.bondable_mode,
phase_args.level, std::move(complete_cb));
} else {
phase_1_ = Phase1::CreatePhase1Responder(
sm_chan_->GetWeakPtr(), listener_->as_weak_ptr(),
phase_args.preq, phase_args.io_capability,
phase_args.bondable_mode, phase_args.level,
std::move(complete_cb));
}
其余方法
Phase1
其余方法就是用于执行以下操作的普通 get 方法:
FeatureExchangeBothSupportSCFeaturesHaveSC
测试用例
此测试用例用于验证如果配对所涉及的两个设备都支持
功能,本例中为安全连接 (SC) 功能,PairingFeatures
由 Phase1
的完整回调返回的正确报告此问题。
默认的 NewPhase1
参数
不支持安全连接,因此代码集
Phase1Args
的 SC 字段,并将其余字段保留为默认值 NewPhase1
:
Phase1Args args;
args.sc_supported = true;
NewPhase1(Role::kInitiator, args);
此测试用例中使用的 L2CAP 消息已被写出,
被测 (kSC
) 集:
const auto kRequest = StaticByteBuffer(
// [...omitted]
AuthReq::kSC | AuthReq::kBondingFlag,
// [...omitted]
);
const auto kResponse = StaticByteBuffer(
// [...omitted]
AuthReq::kSC | AuthReq::kBondingFlag,
// [...omitted]
);
bt-host
的某些部分在异步任务调度程序上运行。在此示例中
FakeChannelTest
在调度程序上运行其 FakeChannel
。Phase1::Start
,
执行 Phase1
的工作,也需要在此调度程序上运行。
PostTask
将 Start
方法放到调度程序上。
然后,FakeChannelTest::Expect
会运行调度程序,并检查下一个
Phase1
发送给 L2CAP 的消息为 kRequest
:
// Initiate the request in a loop task for Expect to detect it.
async::PostTask(dispatcher(), [this] { phase_1()->Start(); });
ASSERT_TRUE(Expect(kRequest));
fake_chan
用于模拟接收来自对等端的响应,
完成第 1 阶段功能交换。在这种情况下,代码会
任务调度程序循环(通过调用 RunLoopUntilIdle()
),而
FakeChannelTest::Expect
在内部做到了这一点:
fake_chan()->Receive(kResponse);
RunLoopUntilIdle();
最后,该代码会验证并确保:
Phase1
未向FakeListener
通知错误- 所有预期参数都会在
Phase1
的完整回调中传递。
EXPECT_EQ(0, listener()->pairing_error_count());
EXPECT_EQ(1, feature_exchange_count());
EXPECT_TRUE(features().initiator);
EXPECT_TRUE(features().secure_connections);
ASSERT_TRUE(last_preq());
ASSERT_TRUE(last_pres());
EXPECT_TRUE(ContainersEqual(kRequest, *last_preq()));
EXPECT_TRUE(ContainersEqual(kResponse, *last_pres()));