Gitlab CI 对erlang应用进行自动测试

Common Test测试框架

  common test是erlang自带的一个测试框架,对于复杂,大规模,特别是分布式的测试场景,它尤为合适。我们将先用它来编写测试计划,然后再编写gitlab ci的测试脚本来调用这些测试计划,从而实现自动测试。

基本结构

  common test由内而外可分为四层。最内层,也即粒度最小的结构,是test case。它们是一个个函数,每一个函数都作为一个测试用例来测试一个小的功能点。包裹一组test case的模块是test suite,他们把一些针对系统同一部件的测试组织在一起。许多test suite组成test object directory。所有test object directory都在test root目录里。由此从内而外,由下而上,一层一层地搭建起针对整个erlang系统的测试,力求覆盖系统的方方面面。

基本测试方法

  我们一般针对test suite来运行测试。为了简化测试的过程,更好地组织起对多个test suite的测试计划,common test还有一个非常强大的测试工具:test spec。对于复杂特别是分布式的测试场景,我们一般使用test spec。它实际上就是让我们编写一个配置文件,利用提供的一些配置元组来配置我们的测试计划。除了erlang原生的一些执行common test的指令外,rebar3也对common test有很好的支持,下面来总结一下erlang原生自带的和rebar3下分别是如何执行common test测试的。

erlang原生
针对test suite进行测试

  linux shell下直接运行:

1
ct_run -suite *****_SUITE

  erl shell里运行:

1
ct:run_test([{suite, *****_SUITE}])
运行test spec

  linux shell下直接运行:

1
ct_run -spec ****.spec

  erl shell里运行:

1
ct:run_test([{spec, "****.spec"}])

  但是,对于需要执行分布式测试的test spec,则只能在erl shell里运行:

1
ct_master:run("****.spec")
rebar3
针对test suite进行测试

  如果应用是用rebar3建立的,那么运行测试非常简单。只需要执行:

1
rebar3 ct

  会自动运行应用内test文件夹下所有的test suite文件。还可以指定执行哪些test suite:

1
rebar3 ct -suite=***_SUITE
运行test spec
1
rebar3 ct -spec=***.spec

  可以用上述方法指定执行哪一个或那几个spec文件。但是,如果spec文件执行的是分布式测试,那么测试会忽略掉。目前,分布式测试只找到用erlang原生的函数来执行的办法。

Gitlab CI进行erlang分布式测试

遇到的问题

Gitlab CI service不能更改hostname

  如果要搭建多个虚拟主机的erlang分布式自动测试环境,必须利用Gitlab CI service功能,它底下是用docker link实现对多个docker container的通信,一个service就是一个docker container,即一个虚拟主机。但目前Gitlab CI还不能更改service的hostname。hostname默认是由service的image名字决定,这样就使得一个镜像只能创建一个service,难以创建多个erlang结点。

  虽然已经有很多Gitlab CI用户反映需要有更改hostname的需求,Gitlab社区版也有了相应的merge request,并且Gitlab官方人员貌似有意在不久的将来把这个功能加进去,但目前还是难以实现。Gitlab CI service的初衷应该主要是一个协助主测试环境的一个单独的虚拟服务器,例如数据库服务器。对于搭建这种分布式环境目前恐怕仍是支持不足。

erlang的docker镜像默认执行的启动指令返回值非0

  erlang的docker镜像在启动以后,会默认执行指令erl,即开启一个erl shell。但这个指令返回值不是0,而Gitlab CI是利用返回值是否为0判断是否出错。如果启动一个erlang的service,系统会报错,使得service启动失败。

目前的解决方案

  通过在同一个虚拟主机里创建多个虚拟结点的方法来搭建分布式环境,而不把分布式环境搭在多个虚拟主机里。

简单的erlang程序自动测试例子:

  .gitlab-ci.yml文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
image: erlang:18.3.4
stages:
- test
compile:
stage: test
script:
- mkdir ebin
- mkdir logs
- cd ebin
- erlc ../src/meeting.erl
- erl -noshell -sname ct -run ct_master run "../test/spec.spec" -s init stop
- vLines=$(grep -vc '<font color=\"red\">' ../logs/ct_run.c*/index.html)
- all=$(cat ../logs/ct_run.c*/index.html | wc -l)
- status1=$(($all-$vLines))
- vLines=$(grep -vc '<font color=\"red\">' ../logs/ct_run.d*/index.html)
- all=$(cat ../logs/ct_run.d*/index.html | wc -l)
- status2=$(($all-$vLines))
- status=$(($status1+$status2))
- exit $status

  spec.spec文件:

1
2
3
4
5
6
7
8
9
10
{node, c, 'c@localhost'}.
{node, d, 'd@localhost'}.
{init, [c,d], [{node_start, [{monitor_master, true}, {erl_flags, "-pa /builds/wangjiasheng/ct-test/ebin/"}]}]}.
{alias, meeting, "./meeting/"}.
{logdir, all_nodes, "../logs/"}.
{logdir, master, "../logs/"}.
{suites, [c,d], meeting, all}.

  这里在本机建立两个erlang结点c@localhost和d@localhost。这两个结点都会运行相同的test suite。其中最核心的执行测试的指令就是:

1
erl -noshell -sname ct -run ct_master run "../test/spec.spec" -s init stop

注意事项

更改docker container的hostname

  在编写spec文件时候,需要指定启动哪些erlang结点,需要知道该结点的sname。然而,Gitlab CI默认的hostname是一长串字符,类似runner-ff7494da-project-564-concurrent-0这种,是运行时动态生成的,并且对该虚拟主机的访问只能通过这个hostname,localhost也是不能用的。因此我们需要自定义一个固定的hostname。

  只需要在Gitlab CI服务器里修改/srv/gitlab-runner/config/config.toml文件,在相应的gitlab runner配置信息里加上hostname,例如这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
[[runners]]
name = "ct-runner"
url = "http://gitlab.local/ci"
token = "ff7494dabc6a295333f685a90a7198"
executor = "docker"
[runners.docker]
hostname = "localhost"
tls_verify = false
image = "erlang:18.3.4"
privileged = false
disable_cache = false
volumes = ["/cache"]
extra_hosts = ["gitlab.local:10.106.128.234"]
[runners.cache]
自行判断测试是否通过

  如果在单结点环境下进行测试,无论运行test suite还是test spec,测试正确状态码都是0,错误都是1。这样Gitlab CI的运行结果是能正确体现测试结果的。但如果运行分布式测试,Gitlab CI的运行结果只反映是否连通其他结点。只要连通了,无论测试结果如何状态码都是0。这就需要我们自己判断测试是否通过。

  每次运行完测试,common test都会生成html格式的报告文件。如果有错误的话,会有错误次数显示,并用红色标注。因此,我们可以通过检索每个结点相应的html文件里是否出现<font color="red">这个关键字,来判断测试是否通过。

  可以用grep命令提取关键字出现的次数,但要注意grep命令如果找不到关键字的话,其返回码是1!!会导致Gitlab CI崩溃。这就与我们找不到关键字代表结果正确这个需求恰恰相反了。所以如果用这个命令来做匹配,需要绕点弯,像上面例子那样,多做几步操作。