测试使用skywalking_SkyWalking agent 插件自动化测试实践-阿里云开发者社区

前言

本文主要介绍SkyWalking agent 插件自动化测试框架组成及测试流程,以及一个实际的自动化测试testcase。

SkyWalking 插件自动化测试框架介绍

相关文档:

这个自动化测试框架主要包含下面几个部分:

测试环境docker镜像

自动化测试脚本

testcase工程

结果验证工具

测试环境docker镜像

提供了两种测试环境:JVM-container 和 Tomcat-container,可以在创建testcase工程时选择使用的测试环境,官方推荐使用JVM-container。

其中JVM-container 可以理解为用于运行基于SpringBoot的testcase项目,包含启动脚本,可以修改启动的JVM参数,灵活性更好。

自动化测试脚本

主要使用到两个脚本:

创建testcase工程

${SKYWALKING_HOME}/test/plugin/generator.sh

执行脚本后,根据提示输入testcase的类型,名称等信息,脚本自动创建一个可以编译运行的样例项目。

运行测试案例

${SKYWALKING_HOME}/test/plugin/run.sh ${scenario_name}

${SKYWALKING_HOME}/test/plugin/run.sh -f ${scenario_name}

${SKYWALKING_HOME}/test/plugin/run.sh --debug ${scenario_name}

参数说明:

-f 参数强制重新创建镜像,在修改SkyWalking agent或plugin后需要添加-f参数,否则不能更新测试镜像中的agent程序。只改动testcase时不需要-f参数,减少启动时间。

--debug 启用调试模式,推荐使用此参数,可以保留测试过程的logs。

testcase工程

这里只介绍JVM-container类型的工程,实际上为基于SpringBoot的testcase应用。

[plugin-scenario]

|- [bin]

|- startup.sh

|- [config]

|- expectedData.yaml

|- [src]

|- [main]

|- ...

|- [resource]

|- log4j2.xml

|- pom.xml

|- configuration.yaml

|- support-version.list

[] = directory

工程文件说明:

文件/目录

说明

bin/startup.sh

testcase 应用启动脚本

config/expectedData.yaml

测试结果验证数据

configuration.yaml

testcase 配置,包含类型、启动脚本、检测url等

support-version.list

testcase支持的版本列表,默认为空不会进行检查,可以改为all表示全部

pom.xml

maven 项目描述文件

[src]

testcase 源码目录

其中对新手来说最难的是编写测试结果验证数据expectedData.yaml,数据格式不是很复杂,但要手写出来还是比较困难的。后面会提及一些技巧,可以从日志文件logs/validatolr.out中提取验证数据。

测试结果验证工具

SkyWalking 自动化测试工具的精髓所做应该就是自动验证测试结果数据,支持多种匹配条件表达式,可以灵活处理一些动态变化的数据。其中关键的是skywalking-validator-tools.jar工具,其源码repo为skywalking-agent-test-tool。

validator的代码量不大,通过阅读代码,可以了解expectedData.yaml的验证过程,理解验证数据的格式。

自动化测试流程

bash ./test/plugin/run.sh --debug xxxx-scenario

-> 准备测试的workspace

-> 编译testcase工程

-> 启动plugin-runner-helper 生成docker启动脚本等

-> scenario.sh

-> 启动测试环境docker实例

-> docker容器中执行 /run.sh

-> collector-startup.sh

-> 启动skywalking-mock-collector(测试数据收集服务)

-> testcase/bin/startup.sh

-> 启动testcase应用(-javaagent加载skywalking-agent.jar)

-> 循环healthCheck,等待testcase应用启动完毕

-> 访问entryService url,触发测试用例

-> 接收测试数据,写入到data/actualData.yaml文件

-> 启动skywalking-validator-tools.jar验证测试结果数据

-> 结束

设计测试用例

测试结果验证工具只能收集testcase应用的APM数据,比如span和logEvent等,不能收集http请求的返回内容,但可以收集到请求的状态码。

测试用例交互过程

这里仅介绍通过http请求交互,收集http相关数据,其它的数据与具体插件相关。比如测试redis apm插件时,可以收集到redis事件,包含执行的redis命令语句。

通过test/plugin/generator.sh命令生成测试用例中包含两个url,一个是healthCheck,一个是entryService。

1)healthCheck一般不需要管,用于探测testcase应用是否启动成功。如果编写的testcase有需要初始化的数据,请在healthCheck返回成功之前进行处理。

2)entryService是测试的入口url,healthCheck通过后,会接着访问entryService。可以在entryService的方法中进行调用测试方法,失败时返回4xx/5xx状态码。

这里要注意一个问题:

org.apache.skywalking.apm.testcase.*包下面的类不会被SkyWalking agent增强,这意味着这个包里面所有的类都不会被插件增强处理,比如标注了@Controller、@Component等的类并不会被apm-spring-annotation-plugin-*.jar 插件增强。如果要测试类增强的相关代码在testcase中,则要将代码放到这个包里面test.org.apache.skywalking.apm.testcase.*。

如何通过收集的APM数据判断测试成功或者失败?

1)http处理成功返回200/3xx时收集到span信息没有status_code,处理异常返回4xx/5xx错误时会产生一个tag记录status_code,可以用于验证区分测试结果。参考代码如下:

@RequestMapping("/dosomething")

public ResponseEntity dosomething() {

// check testcase is successful or not

if (isTestSuccess()) {

return ResponseEntity.ok("success");

} else {

return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("failure");

}

}

2)还可以通过抛出异常来产生status_code和logEvent

@RequestMapping("/xxx-scenario")

@ResponseBody

public String testcase() throws HttpStatusCodeException {

if (isTestSuccess()) {

return "success";

}

throw new RuntimeException("failure");

}

编写测试结果验证数据(expectedData.yaml)

启用调试模式 (--debug),保留日志文件目录logs

bash ./test/plugin/run.sh --debug mytest-scenario

从日志文件提取收集的数据

日志文件目录:skywalking/test/plugin/workspace/mytest-scenario/all/logs

收集的数据可以从日志文件validatolr.out从提取到,找到后面的actual data:

[2020-06-10 07:31:56:674] [INFO] - org.apache.skywalking.plugin.test.agent.tool.validator.assertor.DataAssert.assertEquals(DataAssert.java:29) - actual data:

{

"segmentItems": [

{

"serviceName": "mytest-scenario",

"segmentSize": "2",

"segments": [

{

....

}

]

}

]

}

将actual data的json数据转换为yaml

打开其他测试场景的expectedData.yaml,如httpclient-3.x-scenario/config/expectedData.yaml 的第一段内容:

segmentItems:

- serviceName: httpclient-3.x-scenario

segmentSize: ge 3

segments:

- segmentId: not null

spans:

- operationName: /httpclient-3.x-scenario/case/context-propagate

operationId: 0

parentSpanId: -1

spanId: 0

spanLayer: Http

startTime: nq 0

endTime: nq 0

componentId: 1

isError: false

spanType: Entry

peer: ''

tags:

- {key: url, value: 'http://localhost:8080/httpclient-3.x-scenario/case/context-propagate'}

- {key: http.method, value: GET}

refs:

- {parentEndpoint: /httpclient-3.x-scenario/case/httpclient, networkAddress: 'localhost:8080',

refType: CrossProcess, parentSpanId: 1, parentTraceSegmentId: not null, parentServiceInstance: not

null, parentService: httpclient-3.x-scenario, traceId: not null}

skipAnalysis: 'false'

expectedData.yaml是用于检查测试结果的匹配模板,只有简单的几种匹配表达式如下表:

Operator for number

| Operator | Description |

| :--- | :--- |

| nq | Not equal |

| eq | Equal(default) |

| ge | Greater than or equal |

| gt | Greater than |

Operator for String

| Operator | Description |

| :--- | :--- |

| not null | Not null |

| null | Null or empty String |

| eq | Equal(default) |

比如segmentId是随机生成的,那么可以写成segmentId: not null 这样就可以匹配任意字符串。开始时间是变化的可以写成startTime: nq 0,只判断其是否大于0就可以。

对照获取到的actual data json,修改对应的字段就可以了。可以忽略检查healthCheck的数据,只需要写上关键的segment。看一个案例,actual data 如下:

{

"segmentItems": [

{

"serviceName": "mytest-scenario",

"segmentSize": "2",

"segments": [

{

... healthCheck ...

},

{

"segmentId": "ab32f6a2774347958318b0fb06ccd2f0.33.15917743102950000",

"spans": [

{

"operationName": "/case/mytest-scenario",

"operationId": "0",

"parentSpanId": "-1",

"spanId": "0",

"spanLayer": "Http",

"tags": [

{

"key": "url",

"value": "http://localhost:8080/case/mytest-scenario"

},

{

"key": "http.method",

"value": "GET"

}

],

"startTime": "1591774310295",

"endTime": "1591774310316",

"componentId": "14",

"spanType": "Entry",

"peer": "",

"skipAnalysis": "false"

}

]

}

]

}

]

}

对应的expectedData.yaml(忽略检查healthCheck的数据):

segmentItems:

- serviceName: mytest-scenario

segmentSize: ge 1

segments:

- segmentId: not null

spans:

- operationName: /case/mytest-scenario

operationId: 0

parentSpanId: -1

spanId: 0

spanLayer: Http

startTime: nq 0

endTime: nq 0

componentId: ge 1

isError: false

spanType: Entry

peer: ''

tags:

- {key: url, value: 'http://localhost:8080/case/mytest-scenario'}

- {key: http.method, value: GET}

skipAnalysis: 'false'

常见错误处理

docker 容器实例名冲突

./test/plugin/run.sh 出现下面的错误:

docker: Error response from daemon: Conflict. The container name "/xxxx-scenario-all-local" is already in use by container "42cdee17e557bb71...". You have to remove (or rename) that container to be able to reuse that name.

解决办法:

删除上次测试失败留下来的容器实例:docker rm xxxx-scenario-all-local

编写自动化测试testcase

1. 生成testcase工程

> cd skywalking

> bash ./test/plugin/generator.sh

Sets the scenario name

>: mytest-scenario

Chooses a type of container, 'jvm' or 'tomcat', which is 'jvm-container' or 'tomcat-container'

>: jvm

Gives an artifactId for your project (default: mytest-scenario)

>:

Sets the entry name of scenario (default: mytest-scenario)

>:

scenario_home: mytest-scenario

type: jvm

artifactId: mytest-scenario

scenario_case: mytest-scenario

Please confirm: [Y/N]

>: y

[INFO] Scanning for projects...

2. 修改配置文件

修改mytest-scenario/support-version.list,添加支持的版本,这里用全部版本all。注意,默认没有指定版本,不会启动测试场景。

# lists your version here

all

3. 编写测试用例

@RestController

@RequestMapping("/case")

public class CaseController {

private static final String SUCCESS = "Success";

@RequestMapping("/mytest-scenario")

@ResponseBody

public ResponseEntity testcase() {

//这里简单模拟,随机返回成功或者失败

SecureRandom random = new SecureRandom();

if (random.nextBoolean()) {

return ResponseEntity.ok(SUCCESS);

} else {

return ResponseEntity.notFound().build();

}

}

@RequestMapping("/healthCheck")

@ResponseBody

public String healthCheck() {

// your codes

return SUCCESS;

}

}

4. 本地测试testcase

bash ./test/plugin/run.sh --debug mytest-scenario

5. 提取测试收集的数据

从日志文件提取收集的数据,actual data部分。

日志文件:skywalking/test/plugin/workspace/mytest-scenario/all/logs/validatolr.out

[2020-06-10 09:00:03:655] [INFO] - org.apache.skywalking.plugin.test.agent.tool.validator.assertor.DataAssert.assertEquals(DataAssert.java:29) - actual data:

{

"segmentItems": [

{

"serviceName": "mytest-scenario",

"segmentSize": "2",

"segments": [

{

"segmentId": "bfddda9bb70f49c694a90924b258a6da.32.15917795967760000",

"spans": [

{

"operationName": "/mytest-scenario/case/healthCheck",

"operationId": "0",

"parentSpanId": "-1",

"spanId": "0",

"spanLayer": "Http",

"tags": [

{

"key": "url",

"value": "http://localhost:8080/mytest-scenario/case/healthCheck"

},

{

"key": "http.method",

"value": "HEAD"

}

],

"startTime": "1591779596801",

"endTime": "1591779597069",

"componentId": "1",

"spanType": "Entry",

"peer": "",

"skipAnalysis": "false"

}

]

},

{

"segmentId": "bfddda9bb70f49c694a90924b258a6da.33.15917795971310000",

"spans": [

{

"operationName": "/mytest-scenario/case/mytest-scenario",

"operationId": "0",

"parentSpanId": "-1",

"spanId": "0",

"spanLayer": "Http",

"tags": [

{

"key": "url",

"value": "http://localhost:8080/mytest-scenario/case/mytest-scenario"

},

{

"key": "http.method",

"value": "GET"

}

],

"startTime": "1591779597132",

"endTime": "1591779597141",

"componentId": "1",

"spanType": "Entry",

"peer": "",

"skipAnalysis": "false"

}

]

}

]

}

]

}

6. 编写expectedData.yaml

segmentItems:

- serviceName: mytest-scenario

segmentSize: ge 1

segments:

- segmentId: not null

spans:

- operationName: /case/mytest-scenario

operationId: 0

parentSpanId: -1

spanId: 0

spanLayer: Http

startTime: nq 0

endTime: nq 0

componentId: ge 1

isError: false

spanType: Entry

peer: ''

tags:

- {key: url, value: 'http://localhost:8080/case/mytest-scenario'}

- {key: http.method, value: GET}

skipAnalysis: 'false'

你可能感兴趣的:(测试使用skywalking)