可以使用 Realm Builder 程式庫,以便進行整合測試 允許在執行階段建構領域和 個別測試案例專用的模擬元件。
如果測試想要啟動子項元件,那麼 Realm Builder 可能會是 適合輔助測試
如果測試沒有好處,那就是針對每項測試量身打造測試領域 案例或領域,包含每個測試案例專屬的模擬元件 實作、瞭解和維護 靜態元件資訊清單。如果測試呼叫 Realm Builder 很適合用來協助測試
Realm Builder 程式庫支援多種語言, 每種語言提供的語義和功能可能有所不同。如需 功能清單和支援的語言,請參閱 語言特徵矩陣。
新增依附元件
Realm Builder 用戶端程式庫需要特殊功能才能運作。因此 使用此程式庫的測試必須在其容器中加入必要的資料分割 測試元件的資訊清單:
include: [
"sys/component/realm_builder.shard.cml",
// ...
],
之後,您應針對測試的語言新增 GN 依附元件:
Rust
將 Rust Realm Builder 程式庫新增至 BUILD.gn
檔案
deps = [
"//src/lib/fuchsia-component-test",
# ...
]
C++
在 BUILD.gn
檔案中加入 C++ Realm Builder 程式庫
deps = [
"//sdk/lib/sys/component/cpp/testing:cpp",
# ...
]
初始化 Realm Builder
新增必要的依附元件後,請在 測試元件
建議:初始化獨立的建構工具執行個體 。
不建議使用:在以下兩個位置使用共用建構工具執行個體: 所有測試案例。
Rust
本節假設您要撰寫非同步測試,且 元件的某些部分看起來與以下內容類似:
#[fuchsia::test]
async fn test() -> Result<(), Error> {
// ...
}
匯入 Realm Builder 程式庫
use {
// ...
fuchsia_component_test::{
Capability, ChildOptions, LocalComponentHandles, RealmBuilder, Ref, Route,
},
futures::{StreamExt, TryStreamExt},
};
初始化 RealmBuilder
結構
為測試中的每個測試案例建立新的 RealmBuilder
執行個體。
這會建立獨特的隔離子項領域,確保副作用
不會影響其他測試案例
let builder = RealmBuilder::new().await?;
C++
本節假設您要撰寫非同步測試,且 測試正在訊息迴圈中執行。這類案件通常適合 輸入:
#include <lib/async-loop/cpp/loop.h>
#include <lib/async-loop/default.h>
TEST(SampleTest, CallEcho) {
async::Loop loop(&kAsyncLoopConfigAttachToCurrentThread);
// Test code below
}
匯入 Realm Builder 程式庫
#include <lib/sys/component/cpp/testing/realm_builder.h>
使用程式庫命名空間 您可以跳過這個步驟,該程式碼會匯入整個程式庫的命名空間 方便您編寫及讀取測試
// NOLINTNEXTLINE
using namespace component_testing;
初始化 Realm::Builder
類別
為測試中的每個測試案例建立新的 Realm::Builder
執行個體。
這會建立獨特的隔離子項領域,確保副作用
不會影響其他測試案例
auto builder = RealmBuilder::Create();
建造領域
利用建構為目標的 Realm Builder 物件, 組合領域。
使用 Realm Builder 執行個體,利用 新增元件函式每個子元件都需要下列項目:
- 元件名稱:領域內部元件的專屬 ID。
如果是靜態元件,這個屬性會對應至執行個體的
name
屬性 列在元件資訊清單的children
部分。 - 元件來源:定義在運作範圍時建立元件的方式
。如果是靜態元件,這個
URL
應為含有 有效元件網址。這會對應至url
元件的children
部分中列出的執行個體 資訊清單。
以下範例會在已建立的領域中新增兩個靜態子項元件:
- 從絕對元件網址載入元件
echo_server
元件
echo_client
從相對元件網址載入
Rust
// Add component to the realm, which is fetched using a URL.
let echo_server = builder
.add_child(
"echo_server",
"fuchsia-pkg://fuchsia.com/realm-builder-examples#meta/echo_server.cm",
ChildOptions::new(),
)
.await?;
// Add component to the realm, which is fetched using a fragment-only URL.
// The child is not exposing a service, so the `eager` option ensures the
// child starts when the realm is built.
let echo_client = builder
.add_child("echo_client", "#meta/echo_client.cm", ChildOptions::new().eager())
.await?;
C++
// Add component server to the realm, which is fetched using a URL.
builder.AddChild("echo_server",
"fuchsia-pkg://fuchsia.com/realm-builder-examples#meta/echo_server.cm");
// Add component to the realm, which is fetched using a fragment-only URL. The
// child is not exposing a service, so the `EAGER` option ensures the child
// starts when the realm is built.
builder.AddChild("echo_client", "#meta/echo_client.cm",
ChildOptions{.startup_mode = StartupMode::EAGER});
新增模擬元件
模擬元件可讓測試提供本機實作,以做為 專屬元件Realm Builder 實作的通訊協定會啟用 元件架構,將本機實作視為元件和處理 傳入 FIDL 連線本機實作可保留專屬於 測試案例,讓每個建構領域都有一個模型 適合特定用途
下列範例將示範實作
fidl.examples.routing.echo.Echo
通訊協定。
首先,您必須實作模擬元件。
Rust
在 Rust 中,系統會使用下列函式實作模擬元件 簽章:
Missing code example
MockHandles
是包含元件傳入程序的控制代碼
和對外功能:
Missing code example
模擬元件的實作方式如下:
async fn echo_server_mock(handles: LocalComponentHandles) -> Result<(), Error> {
// Create a new ServiceFs to host FIDL protocols from
let mut fs = fserver::ServiceFs::new();
// Add the echo protocol to the ServiceFs
fs.dir("svc").add_fidl_service(IncomingService::Echo);
// Run the ServiceFs on the outgoing directory handle from the mock handles
fs.serve_connection(handles.outgoing_dir)?;
fs.for_each_concurrent(0, move |IncomingService::Echo(stream)| async move {
stream
.map(|result| result.context("Request came with error"))
.try_for_each(|request| async move {
match request {
fecho::EchoRequest::EchoString { value, responder } => {
responder
.send(value.as_ref().map(|s| &**s))
.expect("failed to send echo response");
}
}
Ok(())
})
.await
.context("Failed to serve request stream")
.unwrap_or_else(|e| eprintln!("Error encountered: {:?}", e))
})
.await;
Ok(())
}
C++
在 C++ 中,如要實作模擬元件,您可以建立沿用自
LocalComponent 介面,並覆寫 Start
方法。
// The interface for backing implementations of components with a Source of Mock.
class LocalComponentImpl {
public:
virtual ~LocalComponentImpl();
// Invoked when the Component Manager issues a Start request to the component.
// |mock_handles| contains the outgoing directory and namespace of
// the component.
virtual void OnStart() = 0;
// The LocalComponentImpl derived class may override this method to be informed if
// ComponentController::Stop() was called on the controller associated with
// the component instance. The ComponentController binding will be dropped
// automatically, immediately after LocalComponentImpl::OnStop() returns.
virtual void OnStop() {}
// The component can call this method to terminate its instance. This will
// release the handles, and drop the |ComponentController|, informing
// component manager that the component has stopped. Calling |Exit()| will
// also cause the Realm to drop the |LocalComponentImpl|, which should
// destruct the component, and the handles and bindings held by the component.
// Therefore the |LocalComponentImpl| should not do anything else after
// calling |Exit()|.
//
// This method is not valid until |OnStart()| is invoked.
void Exit(zx_status_t return_code = ZX_OK);
// Returns the namespace provided to the mock component.
//
// This method is not valid until |OnStart()| is invoked.
fdio_ns_t* ns();
// Returns a wrapper around the component's outgoing directory. The mock
// component may publish capabilities using the returned object.
//
// This method is not valid until |OnStart()| is invoked.
sys::OutgoingDirectory* outgoing();
// Convenience method to construct a ServiceDirectory by opening a handle to
// "/svc" in the namespace object returned by `ns()`.
//
// This method is not valid until |OnStart()| is invoked.
sys::ServiceDirectory svc();
private:
friend internal::LocalComponentRunner;
// The |LocalComponentHandles| are set by the |LocalComponentRunner| after
// construction by the factory, and before calling |OnStart()|
std::unique_ptr<LocalComponentHandles> handles_;
};
LocalComponentHandles
類別包含對元件傳入的控制代碼
和對外功能:
// Handles provided to mock component.
class LocalComponentHandles final {
public:
// ...
// Returns the namespace provided to the mock component. The returned pointer
// will be invalid once *this is destroyed.
fdio_ns_t* ns();
// Returns a wrapper around the component's outgoing directory. The mock
// component may publish capabilities using the returned object. The returned
// pointer will be invalid once *this is destroyed.
sys::OutgoingDirectory* outgoing();
// Convenience method to construct a ServiceDirectory by opening a handle to
// "/svc" in the namespace object returned by `ns()`.
sys::ServiceDirectory svc();
// ...
};
模擬元件的實作方式如下:
class LocalEchoServerImpl : public fidl::examples::routing::echo::Echo, public LocalComponentImpl {
public:
explicit LocalEchoServerImpl(async_dispatcher_t* dispatcher) : dispatcher_(dispatcher) {}
// Override `OnStart` from `LocalComponentImpl` class.
void OnStart() override {
// When `OnStart()` is called, this implementation can call methods to
// access handles to the component's incoming capabilities (`ns()` and
// `svc()`) and outgoing capabilities (`outgoing()`).
ASSERT_EQ(outgoing()->AddPublicService(bindings_.GetHandler(this, dispatcher_)), ZX_OK);
}
// Override `EchoString` from `Echo` protocol.
void EchoString(::fidl::StringPtr value, EchoStringCallback callback) override {
callback(std::move(value));
}
private:
async_dispatcher_t* dispatcher_;
fidl::BindingSet<fidl::examples::routing::echo::Echo> bindings_;
};
模擬實作完成後,您就可以加入領域:
Rust
let echo_server = builder
.add_local_child(
"echo_server",
move |handles: LocalComponentHandles| Box::pin(echo_server_mock(handles)),
ChildOptions::new(),
)
.await?;
C++
// Add component to the realm, providing a mock implementation
builder.AddLocalChild("echo_server",
[&]() { return std::make_unique<LocalEchoServerImpl>(dispatcher()); });
路線功能
根據預設,已建立的領域中沒有任何功能路徑。 如要使用 Realm Builder 將功能轉送至元件,請呼叫新增路徑 函式。
子項元件之間的轉送
以下範例將能力路徑新增至 offer 元件
echo_client
從元件中運用 fidl.examples.routing.echo.Echo
通訊協定
echo_server
。
Rust
builder
.add_route(
Route::new()
.capability(Capability::protocol_by_name("fidl.examples.routing.echo.Echo"))
.from(&echo_server)
.to(&echo_client),
)
.await?;
C++
builder.AddRoute(Route{.capabilities = {Protocol{"fidl.examples.routing.echo.Echo"}},
.source = ChildRef{"echo_server"},
.targets = {ChildRef{"echo_client"}}});
公開領域功能
為了將所建立領域內部提供的功能轉送至測試元件,
將能力路徑的目標設為 parent
。
已建立的領域會自動 exposes
具備
父項。這可讓 Realm Builder 執行個體存取公開的能力。
以下範例會將 fidl.examples.routing.echo.Echo
通訊協定公開給
父項測試元件
Rust
builder
.add_route(
Route::new()
.capability(Capability::protocol_by_name("fidl.examples.routing.echo.Echo"))
.from(&echo_server)
.to(Ref::parent()),
)
.await?;
C++
builder.AddRoute(Route{.capabilities = {Protocol{"fidl.examples.routing.echo.Echo"}},
.source = ChildRef{"echo_server"},
.targets = {ParentRef()}});
提供測試功能
為了將功能從測試元件轉送至已建立的元件
領域,將能力路徑的來源設為 parent
。這包括
Realm Builder 資料分割的測試功能:
{
protocol: [
"fuchsia.diagnostics.ArchiveAccessor",
"fuchsia.inspect.InspectSink",
],
from: "parent",
to: [ "#realm_builder" ],
},
{
event_stream: [
"capability_requested",
"destroyed",
"running_v2",
"started",
"stopped",
],
from: "parent",
to: "#realm_builder",
},
請參考以下範例轉送 fuchsia.logger.LogSink
通訊協定
從測試元件到領域的子項元件:
Rust
builder
.add_route(
Route::new()
.capability(Capability::protocol_by_name("fuchsia.logger.LogSink"))
.from(Ref::parent())
.to(&echo_server)
.to(&echo_client),
)
.await?;
C++
builder.AddRoute(Route{.capabilities = {Protocol{"fuchsia.logger.LogSink"}},
.source = ParentRef(),
.targets = {ChildRef{"echo_server"}, ChildRef{"echo_client"}}});
建立領域
新增測試案例所需的所有元件和路徑後 使用建構方法建立領域,讓其元件準備就緒 此程序的第一步 是將程式碼簽入執行所有單元
Rust
let realm = builder.build().await?;
C++
auto realm = builder.Build(dispatcher());
使用建構方法傳回的領域執行其他工作。
領域中的任何加速元件都會立即執行,並列出所有功能
現在可以透過測試存取使用 parent
轉送的功能。
Rust
let echo = realm.root.connect_to_protocol_at_exposed_dir::<fecho::EchoMarker>()?;
assert_eq!(echo.echo_string(Some("hello")).await?, Some("hello".to_owned()));
建議做法:在領域執行個體上呼叫 destroy()
每次測試結束時,以確保在下一個測試案例前拆除。
不建議使用:等待領域物件結束 範圍,要求元件管理服務刪除領域及其子項。
C++
auto echo = realm.component().ConnectSync<fidl::examples::routing::echo::Echo>();
fidl::StringPtr response;
echo->EchoString("hello", &response);
ASSERT_EQ(response, "hello");
當領域物件超出範圍時,元件管理服務會將其刪除 領域及其子項。
進階設定
修改產生的資訊清單
適用於新增路徑支援的能力轉送功能 方法不夠充分,您可以手動調整資訊清單宣告。 Realm Builder 支援以下元件類型執行此操作:
- 由 Realm Builder 建立的模擬元件。
- 測試元件位於相同套件中的網址元件。
建構領域後:
- 使用建構領域的 get decl 方法,取得 子項資訊清單
- 修改適當的資訊清單屬性。
- 呼叫 取代 decl 方法
Rust
let mut root_decl = builder.get_realm_decl().await?;
// root_decl is mutated in whatever way is needed
builder.replace_realm_decl(root_decl).await?;
let mut a_decl = builder.get_component_decl("a").await?;
// a_decl is mutated in whatever way is needed
builder.replace_component_decl("a", a_decl).await?;
C++
auto root_decl = realm_builder.GetRealmDecl();
// ... root_decl is mutated as needed
realm_builder.ReplaceRealmDecl(std::move(root_decl));
auto a_decl = realm_builder.GetComponentDecl("a");
// ... a_decl is mutated as needed
realm_builder.ReplaceComponentDecl(std::move(a_decl));
為修改的元件新增路徑時,請直接將這些路徑加入 取得資訊清單的建構領域,而不是使用 建構工具執行個體如此可確保路徑能根據 在建立運作範圍時修改元件。
測試元件單元件
Realm Builder 子項元件的 moniker 如下所示:
fuchsia_component_test_collection:child-name/component-name
測試者包含下列元素:
child-name
:為領域集合自動產生的名稱 來自 Realm Builder 程式庫透過呼叫child_name()
取得 建構領域函數component-name
:「元件名稱」提供的參數 建構領域時,會使用Add Component
元件。
如要取得子項名稱,請在建構的領域中叫用以下方法:
Rust
println!("Child Name: {}", realm.root.child_name());
C++
std::cout << "Child Name: " << realm.component().GetChildName() << std::endl;
疑難排解
能力路徑無效
新增路線函式無法驗證功能是否提供正確能力 將測試元件連結至已建立的運作領域
如果您嘗試以 parent
來源轉送功能,卻沒有
對應的優惠,開啟能力開啟要求並不會解析
就會看到類似下列內容的錯誤訊息:
[86842.196][klog][E] [component_manager] ERROR: Required protocol `fidl.examples.routing.echo.Echo` was not available for target component `/core/test_manager/tests:auto-10238282593681900609/test_wrapper/test_root/fuchsia_component_test_
[86842.197][klog][I] collection:auto-4046836611955440668/echo-client`: An `offer from parent` declaration was found at `/core/test_manager/tests:auto-10238282593681900609/test_wrapper/test_root/fuchsia_component_test_colle
[86842.197][klog][I] ction:auto-4046836611955440668` for `fidl.examples.routing.echo.Echo`, but no matching `offer` declaration was found in the parent
進一步瞭解如何正確提供測試功能 控制器,請參閱提供測試功能一節。
語言特徵矩陣
Rust | C++ | |
---|---|---|
舊版元件 | ||
模擬元件 | ||
覆寫設定值 | ||
操控元件宣告 |