用例工程是一个独立的Maven工程。该工程能将工程打包镜像, 并要求提供一个外部能够访问的Web服务用例测试调用链追踪。
以下是用例工程的目录图:
[plugin_testcase]
|__ [config]
| |__ docker-compse.yml
| |__ expectedData.yaml
|__ [src]
| |__ [main]
| | ...
| |__ [resources]
| | ...
|__ pom.xml
|__ testcase.yml
[] = directory
以下是用例工程中配置文件的说明:
文件 | 用途 |
---|---|
docker-compose.xml | 定义用例的docker运行容器环境 |
expectedData.yaml | 定义用例期望生成的Segment的数据 |
testcase.yml | 定义用例的基本信息,如: 被测试框架名称、版本号 |
- 编写用例代码
- 打包并测试用例镜像,确保在没有加载探针时的用例镜像能够正常运行
- 编写期望数据文件
- 编写用例配置文件
- 测试用例
下面以HttpClient插件为例子,HttpClient插件测试两点: 能否正确的创建HttpClient的Span以及能否正确的传递上下文,基于这两点,设计如下用例:
+-------------+ +------------------+ +-------------------------+
| Browser | | Case Servlet | | ContextPropagateServlet |
| | | | | |
+-----|-------+ +---------|--------+ +------------|------------+
| | |
| | |
| WebHttp +-+ |
+------------------------> |-| HttpClient +-+
| |--------------------------------> |-|
| |-| |-|
| |-| |-|
| |-| <--------------------------------|
| |-| +-+
| <--------------------------| |
| +-+ |
| | |
| | |
| | |
| | |
+ + +
pom.xml最佳实践:
- 测试框架的版本号设置为属性变量
- 镜像的版本号设置成属性变量,并且使用框架版本号作为镜像的版本号
具体请参考配置
在配置插件的过程中,以下三个字段需注意:
字段 | 意义 |
---|---|
imageName | 镜像名字。推荐格式: skywalking/xxx-scenario, 比如httpClient工程使用 skywalking/httpclient-scenario |
dockerDirectory | 镜像构建目录。推荐目录: ${project.basedir}/docker |
imageTags | 镜像版本。推荐插件的版本保持一致 |
- 在config目录创建docker-compose.xml
- 编写docker-compose.xml
- 运行maven的docker插件
执行mvn package docker:build
命令打包用例镜像
- 运行用例容器,并确保用例能够正常运行
- 进入
config
目录,并运行docker-compose up
- Dockerfile添加Agent挂载目录
在Dockerfile文件中添加VOLUME
配置项,具体参考配置
- 启动脚本添加Agent启动参数
HttpClient运行在Tomcat中,javaagent参数应该添加在${project.basedir}/docker/catalina.sh
. 具体参考配置
- 添加 mock-collector 镜像¹
- docker-compose文件中添加mock-collector镜像
- 测试用例镜像link上mock-collector镜像,保证容器间的网络可见
具体请参考配置
- 修改docker-compose.xml中用例开放端口和镜像版本
- 将测试用例中的镜像版本替换成
{CASES_IMAGE_VERSION}
, 具体参考 - 将修改服务映射端口替换成
{SERVER_OUTPUT_PORT}
, 具体参考 - 修改Mock Collector映射端口
{COLLECTOR_OUTPUT_PORT}
, 具体参考
- docker-compose文件添加Agent挂载配置
docker-compose文件中添加Agent挂载目录配置。具体参考配置
- 测试用例镜像, 具体参考文档中的测试章节
HttpClient期望数据文件 --- expectedData.yaml
期望数据文件用来描述用例生成的Segment数据,文件主要包含两部分内容:注册项 和 Segment数据. Segment数据中包含对Span的校验和Segment个数的校验。 在介绍文件组成部分之前,先介绍下期望数据中的校验描述符:
数字型字段校验描述符
描述符 | 描述 |
---|---|
nq |
不等于 |
eq |
等于,默认可以不写 |
ge |
大于等于 |
gt |
大于 |
字符串型字段描述符
描述符 | 描述 |
---|---|
not null |
不为null |
null |
空字符或者null |
eq |
精确匹配. 默认可以不写 |
以上就是所有的期望数据中的校验描述符。下面介绍文件的主要组成部分:
注册项数据校验格式
registryItems:
applications:
- APPLICATION_CODE: APPLICATION_ID(int)
...
instances:
- APPLICATION_CODE: INSTANCE_COUNT(int)
...
operationNames:
- APPLICATION_CODE: [ SPAN_OPERATION(string), ... ]
...
以下对各个校验字段的描述:
字段 | 描述 |
---|---|
applications | 注册的Aplication_code和application Id映射关系.目前只需校验不为0即可 |
instances | Application生成的实例数 |
operationNames | 所有预期生成Span的OperationName列表 |
Segments数据校验格式
segments:
-
applicationCode: APPLICATION_CODE(string)
segmentSize: SEGMENT_SIZE(int)
segments:
- segmentId: SEGMENT_ID(string)
spans:
....
以下对各个校验字段的描述:
字段 | 描述 |
---|---|
applicationCode | 待校验Segment的ApplicationCode. |
segmentSize | 待校验Segment的ApplicationCode生成的Segment的数量. |
segmentId | segment的trace ID. |
spans | segment生成的Span列表 |
Span数据校验格式
注意: 期望文件中Segment的Span是按照Span的结束顺序进行排列
operationName: OPERATION_NAME(string)
operationId: SPAN_ID(int)
parentSpanId: PARENT_SPAN_ID(int)
spanId: SPAN_ID(int)
startTime: START_TIME(int)
endTime: END_TIME(int)
isError: IS_ERROR(string: true, false)
spanLayer: SPAN_LAYER(string: DB, RPC_FRAMEWORK, HTTP, MQ, CACHE)
spanType: SPAN_TYPE(string: Exit, Entry, Local )
componentName: COMPONENT_NAME(string)
componentId: COMPONENT_ID(int)
tags:
- {key: TAG_KEY(string), value: TAG_VALUE(string)}
...
logs:
- {key: LOG_KEY(string), value: LOG_VALUE(string)}
...
peer: PEER(string)
peerId: PEER_ID(int)
refs:
- {
parentSpanId: PARENT_SPAN_ID(int),
parentTraceSegmentId: PARENT_TRACE_SEGMENT_ID(string),
entryServiceName: ENTRY_SERVICE_NAME(string),
networkAddress: NETWORK_ADDRESS(string),
parentServiceName: PARENT_SERVICE_NAME(string),
entryApplicationInstanceId: ENTRY_APPLICATION_INSTANCE_ID(int)
}
...
以下对各个校验字段的描述:
字段 | 描述 |
---|---|
operationName | Span的Operation Name |
operationId | OperationName对应的Id, 这个值目前为0 |
parentSpanId | Span的父级Span的Id. 注意: 第一个Span的parentSpanId为-1 |
spanId | Span的Id. 注意: ID是从0开始. |
startTime | Span开始时间. 目前不支持精确匹配,只需判断不为0即可 |
endTime | Span的结束时间.目前不支持精确匹配,只需判断不为0即可 |
isError | 是否出现异常. 如果Span抛出异常或者状态码大于400,该值为true, 否则为false |
componentName | 对应组件的名字。官方提供的Component,则该值为null. |
componentId | 组件对应的ID. 。如果是官方提供的Component,则该值为定义的组件ID |
tags | Span设置的Tag. 注意: tag的顺序即为在插件中设置的顺序 |
logs | Span设置的log. 注意: 顺序为设置Log的顺序 |
SpanLayer | 设置的SpanLayer. 目前可能的值为: DB, RPC_FRAMEWORK, HTTP, MQ, CACHE |
SpanType | Span的类型. 目前的取值为 Exit, Entry, Local |
peer | 访问的远端IP. Exit类型的Span, 该值非空 |
peerId | 访问的远端IP的ID,该值目前为0 |
以下对SegmentRef各个校验字段的描述:
字段 | 描述 |
---|---|
parentSpanId | 调用端的SpanID. 例如HttpClient是由SegmentA的SpanID为1的调用的,所以该值为1 |
parentTraceSegmentId | 调用端的SegmentID. 格式: ${APPLICATION_CODE[SEGMENT_INDEX]}, SEGMENT_INDEX 是相对于期望文件的INDEX. 例如SegmentB由httpclient-case 中的第0个Segment调用的,所以这个值为${httpclient-case[0]} |
entryServiceName | 调用链入口的Segment的服务名词. 例如HttpClient的entryServiceName为/httpclient-case/case/httpclient |
networkAddress | 被调用者的网络地址。例如CaseServlet通过127.0.0.1:8080调用到ContextPropagateServlet,所以这个值为127.0.0.1:8080 |
parentServiceName | 调用端的SpanID等于0的OperationName |
entryApplicationInstanceId | 调用链入口的实例ID。 |
- 编写RegistryItems
HttpClient测试用例中运行在Tomcat容器中,所以httpclient的实例数为1, 并且applicationId不为0。HttpClient Span的OperationName和ContextPropagateServlet生成的Span的OperationName一致,所以operationNames中只有两个operationName.
registryItems:
applications:
- {httpclient-case: nq 0}
instances:
- {httpclient-case: 1}
operationNames:
- httpclient-case: [/httpclient-case/case/httpclient,/httpclient-case/case/context-propagate]
- 编写segmentItems
根据HttpClient用例的运行流程,推断httpclient-case产生两个Segment. 第一个Segment是访问CaseServlet所产生的, 暂且叫它SegmentA
。第二Segment是ContextPropagateServlet所产生的, 暂且叫它SegmentB
.
segments:
- applicationCode: httpclient-case
segmentSize: 2
Skywalking支持Tomcat埋点,所以SegmentA中会包含两个Span,第一个Span是Tomcat的埋点,第二个Span是HttpClient的埋点.
SegmentA的生成的Span数据如下:
- segmentId: not null
spans:
-
operationName: /httpclient-case/case/context-propagate
operationId: eq 0
parentSpanId: 0
spanId: 1
startTime: nq 0
endTime: nq 0
isError: false
spanLayer: Http
spanType: Exit
componentName: null
componentId: eq 2
tags:
- {key: url, value: 'http://127.0.0.1:8080/httpclient-case/case/context-propagate'}
- {key: http.method, value: GET}
logs: []
peer: null
peerId: eq 0
-
operationName: /httpclient-case/case/httpclient
operationId: eq 0
parentSpanId: -1
spanId: 0
startTime: nq 0
endTime: nq 0
spanLayer: Http
isError: false
spanType: Entry
componentName: null
componentId: 1
tags:
- {key: url, value: 'http://localhost:{SERVER_OUTPUT_PORT}/httpclient-case/case/httpclient'}
- {key: http.method, value: GET}
logs: []
peer: null
peerId: eq 0
SegmentB由于Skywalking对于Tomcat进行埋点会产生一个Span,并且SegmentA传递ContextTrace给SegmentB,对于SegmentB需要校验SegmentRef数据.
SegmentB的Span校验数据格式如下:
- segmentId: not null
spans:
-
operationName: /httpclient-case/case/context-propagate
operationId: eq 0
parentSpanId: -1
spanId: 0
tags:
- {key: url, value: 'http://127.0.0.1:8080/httpclient-case/case/context-propagate'}
- {key: http.method, value: GET}
logs: []
startTime: nq 0
endTime: nq 0
spanLayer: Http
isError: false
spanType: Entry
componentName: null
componentId: 1
peer: null
peerId: eq 0
refs:
- {parentSpanId: 1, parentTraceSegmentId: "${httpclient-case[0]}", entryServiceName: "/httpclient-case/case/httpclient", networkAddress: "127.0.0.1:8080",parentServiceName: "/httpclient-case/case/httpclient",entryApplicationInstanceId: nq 0 }
- 添加testcase.yml文件
testcase.yml文件格式如下:
testcase:
request_url: TESTCASE_REQUEST_URL
test_framework: TEST_FRAMEWORK_NAME
support_versions:
- VERSION
以下对各个字段的描述:
字段 | 描述 |
---|---|
request_url | 用例工程暴露的Web服务的地址, URL中的端口用{SERVER_OUTPUT_PORT} 替代. |
test_framework | 测试框架的名字. 例如HttpClient测试工程使用HttpClient作为test_framework |
running_mode | 跑测试框架的模式. TOGETHER(默认可不写), SINGLE, WITH_OPTIONAL 三种模式 例如HttpClient测试工程使用TOGETHER作为running_mode的模式 |
support_versions | 支持框架的版本列表 |
以下为HttpClient的testcase.yaml文件:
testcase:
request_url: http://localhost:{SERVER_OUTPUT_PORT}/httpclient-case/case/httpclient
support_versions:
- 4.3
...
- 4.5.3
- 添加Profile配置
在pom.xml中添加profiles
配置节点。其中每一个profile
就代表中testcase.yaml
中support_versions列表中的一个, profile
中的id命名格式为: ${project.dir_name}-${support_version}
.
HttpClient支持4.3到4.5.3 14个版本, 所以在pom.xml中添加14个profile
,如下:
<profiles>
<profile>
<id>httpclient-4.3.x-scenario-4.5.3</id>
<properties>
<test.framework.version>4.5.3</test.framework.version>
</properties>
</profile>
....
<profile>
<id>httpclient-4.3.x-scenario-4.3</id>
<properties>
<test.framework.version>4.3</test.framework.version>
</properties>
</profile>
</profiles>
运行测试用例目前需要在本地安装以下环境:
- docker
- docker-compose
- maven
- git
# export project_name=httpclient-4.3.x-scenario
# bash ${SKYWALKING_AGENT_TESTCASES_HOME}/deploy-test.sh --scenario ${scenario_name} ${agent_repo} ${agent_repo_branch}
脚本运行参数的描述:
运行参数 | 描述 |
---|---|
--scenario scenario_dir_name | 指定测试用例进行测试. 默认运行所有的测试用例工程 |
agent_repo | Skywalking agent Git仓库地址 |
agent_repo_branch | Skywalking agent 仓库分支 |
更多脚本参数参考bash ${SKYWALKING_AGENT_TESTCASES_HOME}/deploy-test.sh -h
校验结果存放在${SKYWALKING_AGENT_TESTCASES_HOME}/workspace/logs/test_report.log
.
如果校验失败,test_report.log
日志将会显示类似的校验失败异常。
assert failed.
SegmentNotFoundException:
expected:
Segment:
- span[-1, 0] /vertx-core-3-scenario/vertx-core/executeTest
actual:
Segment[1.159.15542729582940000] e
expected: Span[-1, 0] /vertx-core-3-scenario/vertx-core/executeTest
actual: span[-1, 0] #local-message-receiver
reason: [operation name]: expected=>{/vertx-core-3-scenario/vertx-core/executeTest}, actual=>{#local-message-receiver}
更多的校验失败异常以及修复办法,请参看文档
¹ mock-collector
用来模拟Collector来接受探针上传的数据,源码地址:https://github.com/SkywalkingTest/skywalking-mock-collector.git.