本指南詳細說明 SMP_Phase1Test
測試韌體和 FeatureExchangeBothSupportSCFeaturesHaveSC 測試案例,說明如何為 Fuchsia 的 bt-host
驅動程式編寫測試。
bt-host
驅動程式庫會實作大多數的藍牙核心規格 v5.2、磁碟區 3 (主機子系統)。
Fuchsia 專案特別重視自動化測試,而藍牙團隊致力成為測試文化的領導者。
如要合併 Fuchsia 來源樹狀結構中的 bt-host
驅動程式庫變更,您必須為這項變更編寫自動化測試。
說明
bt-host
是相對大型的程式碼集,具有許多抽象層。
以下是 bt-host
驅動程式庫主要元件的圖表:
{#frameworkure-diagram}
圖表中的每個抽象層大致對應至整個通訊協定。
使用 bt-host
測試時,請著重於瞭解目前處理的圖層 (例如 SM) 與其正下方的層 (例如 L2CAP) 之間的關係。
雖然每層可能都有額外的內部抽象層,但這些跨通訊協定關係最常在測試中模擬及/或進行。
資源
bt-host
是以 C++ 編寫,Fuchsia 則使用 gUnit
Googletest 程式庫進行 C++ 測試。
如要使用 bt-host
單元測試,您必須確實瞭解下列資源:
在編寫 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。這可讓測試案例1以不同參數呼叫 New
* 來重新初始化測試韌體。
void NewPhase1(Role role = Role::kInitiator,
Phase1Args phase_args = Phase1Args(),
hci::Connection::LinkType ll_type =
hci::Connection::LinkType::kLE) {
如果是 NewPhase1
,可設定面向如下:
- 裝置角色
- 傳輸類型
- 保留其餘第 1 階段引數的結構,較容易只變更其中一個「default」引數:
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,以確認 LUT 是否能與上方的層正確通訊。
完成回呼
Phase1
會儲存回呼參數。Phase1
完成後,就會透過這個回呼傳回 Phase1
的結果。將 Phase1
執行個體化時,complete_cb
會用做這個回呼。
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
方法,都是簡單的取得方法執行下列操作:
FeatureExchangeBothSupportSCFeaturesHaveSC
個測試案例
此測試案例會確認所使用的兩個裝置是否都支援某項功能 (在此案例中,安全連線 (SC) 功能),Phase1
完整回呼傳回的 PairingFeatures
會正確回報此情況。
預設的 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()));