非常教程

Erlang 20参考手册

common_test

9. External Configuration Data

9.1总则

为了避免在测试套件中对与测试和/或被测系统(SUT)相关的数据值进行硬编码,可以通过Common Test在测试运行开始之前读取的配置文件或字符串来指定数据。外部配置数据可以在不修改使用数据的测试套件的情况下更改测试属性。配置数据示例如下:

  • 测试工厂或其他仪器的地址
  • 用户登录信息
  • 测试所需文件的名称
  • 测试期间要执行的程序名称
  • 测试所需的任何其他变量

9.2语法

配置文件可以包含任意数量的类型元素:

{CfgVarName,Value}.

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

9.3要求和读取配置数据

在测试套件中,在尝试读取测试用例或配置函数中的关联值之前,必须先要求配置变量(CfgVarName在前一个定义中)存在。

require是一个断言声明,它可以是Test Suite Information Functionor的一部分Test Case Information Function。如果所需变量不可用,则跳过测试(除非已指定默认值,请参阅部分Test Case Information Function了解详细信息)。另外,ct:require/1/2可以从测试用例调用函数来检查特定变量是否可用。必须明确检查此函数的返回值,并根据结果采取适当的操作(例如,如果所讨论的变量不存在,则跳过测试用例)。

一个require测试套件告知情况或测试实例信息列表语句看起来像{require,CfgVarName}{require,AliasName,CfgVarName}。参数AliasName和参数CfgVarName相同ct:require/1,2AliasName成为配置变量的别名,并且可以用作对配置数据值的引用。配置变量可以与任意数量的别名关联,但每个名称在同一个测试套件中必须是唯一的。别名的两个主要用途如下:

  • 识别连接(稍后介绍)。
  • 帮助将配置数据调整到测试套件(或测试用例)并提高可读性。

要读取配置变量的值,请使用函数get_config/1,2,3

例子:

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

...

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

9.4使用在多个文件中定义的配置变量

如果配置变量在多个文件中定义,并且您想要访问所有可能的值,请使用函数ct:get_config/3all在选项列表中进行指定。然后将这些值返回到列表中,并且元素的顺序与启动时指定配置文件的顺序相对应。

9.5加密配置文件

如果必须将包含敏感数据的配置文件存储在打开的和共享的目录中,则可以对它们进行加密。

Common Test使用DES3应用程序中的函数对指定文件进行加密Crypto,请调用ct:encrypt_config_file/2,3加密文件,然后将其用作常规配置文件以及其他加密文件或普通文本文件。但是,运行测试时必须提供解密配置文件的密钥。这可以通过标记/选项decrypt_key或者decrypt_file预定义位置中的密钥文件来完成。

Common Test还提供解密功能,ct:decrypt_config_file/2,3用于重新创建原始文本文件。

9.6使用配置数据打开连接

用于使用在支持功能打开连接,例如两种不同的方法,ct_sshct_ftp,和ct_telnet如下:

  • 使用配置目标名称(别名)作为参考。
  • 使用配置变量作为参考。

当使用目标名称来引用配置数据(指定要打开的连接)时,可以在与连接相关的所有后续调用(也用于关闭它)中使用相同的名称作为连接标识。每个目标名称只能有一个打开的连接。如果您尝试使用已与打开的连接关联的名称打开新的连接,Common Test返回已经存在的句柄,以便使用之前打开的连接。此功能可以在有用时调用该函数来打开特定的连接。像这样的操作不一定会打开任何新的连接,除非它是必需的(例如,如果服务器意外关闭了上一个连接,则可能是这种情况)。使用命名连接还可以避免在套件中传递这些连接的句柄引用。

当配置变量名称用作指定连接的数据的引用时,由于打开连接而返回的句柄必须用于所有后续调用(也用于关闭连接)。使用与参考相同的变量名重复调用打开的函数会导致打开多个连接。例如,如果测试用例需要打开到目标节点上同一服务器的多个连接(对每个连接使用相同的配置数据),这可能很有用。

9.7用户特定的配置数据格式

到目前为止所描述的,用户可以使用与文本文件中的键值元组不同的格式指定配置数据。例如,可以从任何文件中读取数据,通过HTTP从Web上获取数据,或从用户特定的进程请求数据。为了支持这一点,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"}]

9.8配置数据处理示例

使用FTP客户端访问远程主机上的文件的配置文件可能如下所示:

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

 {lm_directory, "/test/loadmodules"}.

前面显示的XML版本也可以使用,但是明确指定ct_config_xml要使用回调模块Common Test

以下是如何断言配置数据可用并可用于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(?config(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, 
                  ?config(ftp_handles,Config)).

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

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

9.9用户特定配置处理程序示例

处理驱动程序的简单配置,向外部服务器请求配置数据,可以如下实现:

-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如果config_server.erl在测试执行过程中编译并存在于代码路径中,则此驱动程序的配置字符串可以是:

-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返回变量的新值。

Erlang 20

Erlang 是一种通用的面向并发的编程语言,可应付大规模开发活动的程序设计语言和运行环境。

主页 https://www.erlang.org/
源码 https://github.com/erlang/otp
版本 20
发布版本 20.1