檢視原始碼 外部組態資料

一般

為了避免在測試套件中硬編碼與測試和/或受測系統 (SUT) 相關的資料值,可以改為透過組態檔案或字串來指定資料,Common Test 會在測試執行開始前讀取這些資料。外部組態資料使得可以在不修改使用資料的測試套件的情況下,變更測試屬性。組態資料的範例如下:

  • 測試工廠或其他儀器的位址
  • 使用者登入資訊
  • 測試所需檔案的名稱
  • 測試期間要執行的程式名稱
  • 測試所需的任何其他變數

語法

一個組態檔案可以包含任意數量的以下類型的元素:

{CfgVarName,Value}.

其中

CfgVarName = atom()
Value = term() | [{CfgVarName,Value}]

要求和讀取組態資料

在測試套件中,必須在測試案例或組態函式中嘗試讀取關聯值之前,要求 組態變數(先前定義中的 CfgVarName)存在。

require 是一個斷言陳述式,它可以是 測試套件資訊函式測試案例資訊函式 的一部分。如果要求的變數不可用,則會跳過測試(除非已指定預設值,詳細資訊請參閱 測試案例資訊函式 章節)。此外,可以從測試案例中呼叫函式 ct:require/1/2 以檢查特定變數是否可用。必須明確檢查此函式的回傳值,並根據結果採取適當的動作(例如,如果相關變數不存在,則跳過測試案例)。

測試套件資訊案例或測試案例資訊清單中的 require 陳述式看起來像 {require,CfgVarName}{require,AliasName,CfgVarName}。參數 AliasNameCfgVarNamect:require/1,2 的參數相同。AliasName 成為組態變數的別名,並且可以用作對組態資料值的參考。組態變數可以與任意數量的別名相關聯,但每個名稱在同一個測試套件中必須是唯一的。別名名稱的兩個主要用途如下:

  • 識別連線(稍後說明)。
  • 協助調整組態資料以適應測試套件(或測試案例),並提高可讀性。

要讀取組態變數的值,請使用函式 get_config/1,2,3

範例

suite() ->
    [{require, domain, 'CONN_SPEC_DNS_SUFFIX'}].

...

testcase(Config) ->
    Domain = ct:get_config(domain),
    ...

使用多個檔案中定義的組態變數

如果組態變數在多個檔案中定義,並且您想要存取所有可能的值,請使用函式 ct:get_config/3,並在選項清單中指定 all。然後,這些值會在清單中回傳,而元素的順序對應於啟動時指定的組態檔案順序。

加密的組態檔案

如果包含敏感資料的組態檔案必須儲存在開放且共用的目錄中,則可以將其加密。

要讓 Common Test 使用應用程式 Crypto 中的函式 DES3 加密指定的檔案,請呼叫 ct:encrypt_config_file/2,3。然後,加密的檔案可以像常規組態檔案一樣使用,並與其他加密檔案或普通文字檔案組合使用。但是,在執行測試時,必須提供用於解密組態檔案的金鑰。可以使用旗標/選項 decrypt_keydecrypt_file,或使用預定義位置的金鑰檔案來執行此操作。

Common Test 還提供了用於重新建立原始文字檔案的解密函式 ct:decrypt_config_file/2,3

使用組態資料開啟連線

以下是兩種使用支援函式(例如,ct_sshct_ftpct_telnet)開啟連線的不同方法:

  • 使用組態目標名稱(別名)作為參考。
  • 使用組態變數作為參考。

當使用目標名稱來參考組態資料(指定要開啟的連線)時,相同的名稱可以用作後續所有與連線相關的呼叫(也用於關閉連線)中的連線身分。每個目標名稱只能有一個開啟的連線。如果您嘗試使用已與開啟的連線相關聯的名稱開啟新連線,Common Test 會回傳已存在的控制代碼,因此會使用先前開啟的連線。此功能使得可以在需要時隨時呼叫開啟特定連線的函式。除非有需要,否則這樣的動作不一定會開啟任何新的連線(例如,先前連線被伺服器意外關閉的情況)。使用具名連線也消除了在套件中傳遞這些連線的控制代碼參考的需要。

當使用組態變數名稱作為參考來指定連線的資料時,開啟連線所回傳的控制代碼必須在所有後續呼叫中使用(也用於關閉連線)。使用相同的變數名稱作為參考重複呼叫開啟函式會導致開啟多個連線。如果測試案例需要開啟到目標節點上同一伺服器的多個連線(針對每個連線使用相同的組態資料),這可能會很有用。

使用者特定的組態資料格式

使用者可以使用與目前描述的文字檔中的鍵值元組不同的格式來指定組態資料。例如,可以從任何檔案讀取資料、透過 HTTP 從網路上擷取資料,或從使用者特定的程序請求資料。為了支援此功能,Common Test 提供了回呼模組外掛機制來處理組態資料。

處理組態資料的預設回呼模組

Common Test 包含用於處理以標準組態檔案(先前描述)和 XML 檔案指定的組態資料的預設回呼模組,如下所示:

  • ct_config_plain - 用於讀取具有鍵值元組的組態檔案(標準格式)。如果未指定使用者回呼,則此處理程式用於剖析組態檔案。
  • ct_config_xml - 用於從 XML 檔案讀取組態資料。

使用 XML 組態檔案

以下是 XML 組態檔案的範例:

<config>
   <ftp_host>
       <ftp>"targethost"</ftp>
       <username>"tester"</username>
       <password>"letmein"</password>
   </ftp_host>
   <lm_directory>"/test/loadmodules"</lm_directory>
</config>

讀取後,此檔案會產生與以下文字檔案相同的組態變數:

{ftp_host, [{ftp,"targethost"},
            {username,"tester"},
            {password,"letmein"}]}.

{lm_directory, "/test/loadmodules"}.

實作使用者特定的處理程式

可以編寫使用者特定的處理程式來處理特殊的組態檔案格式。參數可以是檔案名稱或組態字串(空清單是有效的)。

實作處理程式的回呼模組負責檢查組態字串的正確性。

為了驗證組態字串,回呼模組必須匯出函式 Callback:check_parameter/1

輸入引數從 Common Test 傳遞,如測試規格中所定義,或指定為 ct_runct:run_test 的選項。

回傳值應為以下任何值,指示指定的組態參數是否有效:

  • {ok, {file, FileName}} - 參數是檔案名稱,且檔案存在。
  • {ok, {config, ConfigString}} - 參數是組態字串,且它是正確的。
  • {error, {nofile, FileName}} - 目前目錄中沒有具有指定名稱的檔案。
  • {error, {wrong_config, ConfigString}} - 組態字串錯誤。

函式 Callback:read_config/1 將從回呼模組匯出,以讀取組態資料,最初是在測試開始之前,或是在測試執行期間重新載入資料的結果。輸入引數與函式 check_parameter/1 的輸入引數相同。

回傳值應為以下任一值:

  • {ok, Config} - 如果組態變數已成功讀取。
  • {error, {Error, ErrorDetails}} - 如果回呼模組無法繼續指定的組態參數。

Config 是正確的 Erlang 鍵值清單,其值可以是鍵值子清單,如先前的組態檔案範例所示:

[{ftp_host, [{ftp, "targethost"}, {username, "tester"}, {password, "letmein"}]},
 {lm_directory, "/test/loadmodules"}]

組態資料處理的範例

用於使用 FTP 用戶端存取遠端主機上的檔案的組態檔案可能如下所示:

{ftp_host, [{ftp,"targethost"},
            {username,"tester"},
            {password,"letmein"}]}.

{lm_directory, "/test/loadmodules"}.

也可以使用先前顯示的 XML 版本,但必須明確指定 Common Test 要使用 ct_config_xml 回呼模組。

以下範例說明如何斷言組態資料可用且可用於 FTP 工作階段:

init_per_testcase(ftptest, Config) ->
    {ok,_} = ct_ftp:open(ftp),
    Config.

end_per_testcase(ftptest, _Config) ->
    ct_ftp:close(ftp).

ftptest() ->
    [{require,ftp,ftp_host},
     {require,lm_directory}].

ftptest(Config) ->
    Remote = filename:join(ct:get_config(lm_directory), "loadmodX"),
    Local = filename:join(proplists:get_value(priv_dir,Config), "loadmodule"),
    ok = ct_ftp:recv(ftp, Remote, Local),
    ...

以下範例說明如果需要開啟與 FTP 伺服器的多個連線,如何重寫先前範例中的函式:

init_per_testcase(ftptest, Config) ->
    {ok,Handle1} = ct_ftp:open(ftp_host),
    {ok,Handle2} = ct_ftp:open(ftp_host),
    [{ftp_handles,[Handle1,Handle2]} | Config].

end_per_testcase(ftptest, Config) ->
    lists:foreach(fun(Handle) -> ct_ftp:close(Handle) end,
                  proplists:get_value(ftp_handles,Config)).

ftptest() ->
    [{require,ftp_host},
     {require,lm_directory}].

ftptest(Config) ->
    Remote = filename:join(ct:get_config(lm_directory), "loadmodX"),
    Local = filename:join(proplists:get_value(priv_dir,Config), "loadmodule"),
    [Handle | MoreHandles] = proplists:get_value(ftp_handles,Config),
    ok = ct_ftp:recv(Handle, Remote, Local),
    ...

使用者特定的組態處理程式範例

可以實作一個簡單的組態處理驅動程式,該驅動程式會向外部伺服器請求組態資料,如下所示:

-module(config_driver).
-export([read_config/1, check_parameter/1]).

read_config(ServerName)->
    ServerModule = list_to_atom(ServerName),
    ServerModule:start(),
    ServerModule:get_config().

check_parameter(ServerName)->
    ServerModule = list_to_atom(ServerName),
    case code:is_loaded(ServerModule) of
        {file, _}->
            {ok, {config, ServerName}};
        false->
            case code:load_file(ServerModule) of
                {module, ServerModule}->
                    {ok, {config, ServerName}};
                {error, nofile}->
                    {error, {wrong_config, "File not found: " ++ ServerName ++ ".beam"}}
            end
    end.

如果編譯了以下 config_server.erl 模組,並在測試執行期間存在於程式碼路徑中,則此驅動程式的組態字串可以是 config_server

-module(config_server).
-export([start/0, stop/0, init/1, get_config/0, loop/0]).

-define(REGISTERED_NAME, ct_test_config_server).

start()->
    case whereis(?REGISTERED_NAME) of
        undefined->
            spawn(?MODULE, init, [?REGISTERED_NAME]),
            wait();
        _Pid->
        ok
    end,
    ?REGISTERED_NAME.

init(Name)->
    register(Name, self()),
    loop().

get_config()->
    call(self(), get_config).

stop()->
    call(self(), stop).

call(Client, Request)->
    case whereis(?REGISTERED_NAME) of
        undefined->
            {error, {not_started, Request}};
        Pid->
            Pid ! {Client, Request},
            receive
                Reply->
                    {ok, Reply}
            after 4000->
                {error, {timeout, Request}}
            end
    end.

loop()->
    receive
        {Pid, stop}->
            Pid ! ok;
        {Pid, get_config}->
            {D,T} = erlang:localtime(),
            Pid !
                [{localtime, [{date, D}, {time, T}]},
                 {node, erlang:node()},
                 {now, erlang:now()},
                 {config_server_pid, self()},
                 {config_server_vsn, ?vsn}],
            ?MODULE:loop()
    end.

wait()->
    case whereis(?REGISTERED_NAME) of
        undefined->
            wait();
        _Pid->
            ok
    end.

在這裡,處理程式還提供了組態變數的動態重新載入。如果從測試案例函式呼叫 ct:reload_config(localtime),則所有使用 config_driver:read_config/1 載入的變數都會更新為其最新值,並回傳變數 localtime 的新值。