Home > Erlang探索 > rebar和common_test使用实践和疑惑澄清

rebar和common_test使用实践和疑惑澄清

October 19th, 2011

原创文章,转载请注明: 转载自系统技术非业余研究

本文链接地址: rebar和common_test使用实践和疑惑澄清

rebar是个功能非常强大的Erlang项目管理工具,参看这里 https://github.com/basho/rebar,他的定位是:

rebar is an Erlang build tool that makes it easy to compile and
test Erlang applications, port drivers and releases.

rebar is a self-contained Erlang script, so it’s easy to distribute or even
embed directly in a project. Where possible, rebar uses standard Erlang/OTP
conventions for project structures, thus minimizing the amount of build
configuration work. rebar also provides dependency management, enabling
application writers to easily re-use common libraries from a variety of
locations (git, hg, etc).

common_test是Erlang强大的黑盒测试框架 参见这里

Common Test is a portable application for automated testing. It is suitable for black-box testing of target systems of
any type (i.e. not necessarily implemented in Erlang), as well as for white-box testing of Erlang/OTP programs. Blackbox
testing is performed via standard O&M interfaces (such as SNMP, HTTP, Corba, Telnet, etc) and, if required, via
user specific interfaces (often called test ports). White-box testing of Erlang/OTP programs is easily accomplished by
calling the target API functions directly from the test case functions. Common Test also integrates usage of the OTP
cover tool for code coverage analysis of Erlang/OTP programs.
Common Test executes test suite programs automatically, without operator interaction. Test progress and results is
printed to logs on HTML format, easily browsed with a standard web browser. Common Test also sends notifications
about progress and results via an OTP event manager to event handlers plugged in to the system. This way users can
integrate their own programs for e.g. logging, database storing or supervision with Common Test.
Common Test provides libraries that contain useful support functions to fill various testing needs and requirements.
There is for example support for flexible test declarations by means of so called test specifications. There is also
support for central configuration and control of multiple independent test sessions (towards different target systems)
running in parallel.
Common Test is implemented as a framework based on the OTP Test Server application.

但是common_test由于太强大了,新手使用起来会比较麻烦,经常会碰到些问题,不好解决。

这时候rebar来救助了,它的ct功能把common_test使用的麻烦给解决掉了,让你轻松做测试。
我们先来体验下:

$ mkdir foo
$ cd foo/
$ touch rebar.config
$ rebar create-app appid=foo
==> foo (create-app)
Writing src/foo.app.src
Writing src/foo_app.erl
Writing src/foo_sup.erl
$ rebar  create template=ctsuite
==> foo (create)
Writing test/mymodule_SUITE.erl
$ rebar ct suite=mymodule
==> foo (ct)
DONE. Testing chuba.foo.mymodule_SUITE: TEST COMPLETE, 0 ok, 0 failed, 1 skipped of 1 test cases
$ cd logs
$ hostname -i
10.232.31.89
$ python -m SimpleHTTPServer 
Serving HTTP on 0.0.0.0 port 8000 ...

打卡浏览器 http://10.232.31.89:8000我们可以看到:

上面我们演示了rebar的ct功能使用的基本步骤,底下是进阶信息:
我们从rebar_ct.erl中可以看到:

%% Global options:
%% verbose=1 - show output from the common_test run as it goes
%% suite="foo"" - runs <test>/foo_SUITE
%% case="mycase" - runs individual test case foo_SUITE:mycase
...
get_ct_config_file(TestDir) ->
    Config = filename:join(TestDir, "test.config"),
    case filelib:is_regular(Config) of
        false ->        
            " ";
        true ->
            " -ct_config " ++ Config
    end.
...
get_config_file(TestDir) ->
    Config = filename:join(TestDir, "app.config"),
    case filelib:is_regular(Config) of
        false ->
            " ";
        true ->
            " -config " ++ Config
    end.

如果test目录下有app.config 那么会把它用-config app.config传给vm, 这个app.config主要用于设置application的环境变量。

如果有test.config, 那么会把它用 -ct_config test.config传给vm, 这个test.config主要用于配置测试案例的变量。

test.config规格是类似这样的,可以参见common_test userguide里面配置的章节:
{key, value}.
{catalog, [{k1,v1}, {k2, v2}]}.

程序里面要用方式获取:
ct:get_config(key).
ct:get_config({catalog,key}).

获取配置的方式有底下几种,是很容易混淆的地方,我来解释下:

test_mymodule(_Config) ->
    io:format("port ~p~n", [ct:get_config({foo, port})]),  
    io:format("priv_dir ~p~n", [?config(priv_dir, _Config)]),      
    io:format("priv_dir ~p~n", [proplists:get_value(priv_dir, Config)]),      
    ok.

?config(key, Config).只是用于读取config变量里面的值,和proplists:get_value(key, Config)是一样的。
看代码:

-define(config,test_server:lookup_config).
lookup_config(Key,Config) ->
    case lists:keysearch(Key,1,Config) of
    {value,{Key,Val}} ->
            Val;
        _ ->
            io:format("Could not find element ~p in Config.~n",[Key]),
            undefined
    end.

现在我们来演示下test.config的使用:

$ pwd
/home/chuba/foo/test
$ cat test.config 
{foo, [{hostname, "127.0.0.1"},
       {port, 8000}
      ]}.
$ diff mymodule_SUITE.erl mymodule_SUITE.erl.orig
164c164
<     [{require, foo}, {userdata,[{doc,"Testing the mymodule module"}]}].
---
>     [{userdata,[{doc,"Testing the mymodule module"}]}].
167,168c167
<     io:format("port ~p~n", [ct:get_config({foo, port})]),  
<     ok.
---
>     {skip,"Not implemented."}.
$ rebar ct suite=mymodule
==> foo (ct)
DONE. Testing chuba.foo.mymodule_SUITE: TEST COMPLETE, 1 ok, 0 failed of 1 test cases

我们看下现在的执行结果:

当然也可以用ct_run来运行:

$ct_run -suite $PWD/mymodule_SUITE -pa $PWD/ebin -logdir $PWD/logs -config test/test.config 
Erlang R14B04 (erts-5.8.5) [source] [64-bit] [smp:16:16] [rq:16] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.8.5  (abort with ^G)

Common Test v1.5.5 starting (cwd is /home/chuba/foo)

(ct@my031089)1> 
Common Test: Running make in test directories...

CWD set to: "/home/chuba/foo/logs/ct_run.ct@my031089.2011-10-19_11.52.21"

TEST INFO: 1 test(s), 1 case(s) in 1 suite(s)

Testing chuba.foo.mymodule_SUITE: Starting test, 1 test cases
Testing chuba.foo.mymodule_SUITE: TEST COMPLETE, 1 ok, 0 failed of 1 test cases

Updating /home/chuba/foo/logs/index.html... done
Updating /home/chuba/foo/logs/all_runs.html... done

我们可以看到ct_run是通过-config foo.config 这样来传配置文件的,这个地方和容易和erl -config app.config混淆。
实际上ct_run是个程序,我们看下他的代码:

//ct_run.c
...
else if (cnt < erl_args) {
            if (strcmp(argv[1], "-config") == 0)
                PUSH("-ct_config");
            else if (strcmp(argv[1], "-decrypt_key") == 0)
                PUSH("-ct_decrypt_key");
            else if (strcmp(argv[1], "-decrypt_file") == 0)
                PUSH("-ct_decrypt_file");
            else
                PUSH(argv[1]);
        }
...

-config选项会被转化成-ct_config来传递给vm,所以和-config app.config就能分开,但是理解起来确实很恼人。

小结: rebar和common_test配合起来用的很爽。
祝玩得开心。

Post Footer automatically generated by wp-posturl plugin for wordpress.

Categories: Erlang探索 Tags: , ,
  1. sdjcw
    October 26th, 2011 at 14:16 | #1

    [{userdata,[{doc,”Testing the mymodule module”}]}].
    这个修改是做什么呢?

  2. sdjcw
    October 26th, 2011 at 14:17 | #2

    直接贴代码,结果样子变形了。。。
    我想问“{require, foo}”这个是做什么用的?

    sdjcw Reply:

    我查到了,判断config中是否有相关的变量用的。

    Yu Feng Reply:

    对的

  3. sdjcw
    October 26th, 2011 at 15:58 | #3

    我想问下,在rebar中ct的测试覆盖率报告怎么打开?
    还有就是eunit的可以打开,如果ct的也打开了,两个覆盖率不知道能不能合并呢?

    Yu Feng Reply:

    get_cover_config(Config, Cwd) ->
    case rebar_config:get_local(Config, cover_enabled, false) of
    false ->
    “”;
    true ->
    case collect_glob(Cwd, “.*cover\.spec\$”) of
    [] ->
    ?DEBUG(“No cover spec found: ~s~n”, [Cwd]),
    “”;
    [Spec] ->
    ?DEBUG(“Found cover file ~w~n”, [Spec]),
    ” -cover ” ++ Spec;
    Specs ->
    ?ABORT(“Multiple cover specs found: ~p~n”, [Specs])
    end
    end.

    sdjcw Reply:

    非常感谢~我已经能使用ct输出测试覆盖率了,不过没办法和eunit的合并。
    呵呵~就用eunit测试无状态的方法,用ct测试gen_server等吧。

    Yu Feng Reply:

    目前只能分开吧。。。

Comments are closed.