SkyWalking agent 插件自动化测试实践

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
应用实时监控服务ARMS - 应用监控,每月50GB免费额度
简介: ## 前言 本文主要介绍SkyWalking agent 插件自动化测试框架组成及测试流程,以及一个实际的自动化测试testcase。 ## SkyWalking 插件自动化测试框架介绍 相关文档: > [SkyWalking Plugin automatic test framework] (https://github.com/apache/skywalking/blob/m

前言

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

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

相关文档:

[SkyWalking Plugin automatic test framework]
(https://github.com/apache/skywalking/blob/master/docs/en/guides/Plugin-test.md)

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

  1. 测试环境docker镜像
  2. 自动化测试脚本
  3. testcase工程
  4. 结果验证工具

测试环境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)

  1. 启用调试模式 (--debug),保留日志文件目录logs
    bash ./test/plugin/run.sh --debug mytest-scenario
  2. 从日志文件提取收集的数据
    日志文件目录: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": [
        {
        ....
        }
      ]
    }
  ]
}
  1. 将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'
相关实践学习
通过云拨测对指定服务器进行Ping/DNS监测
本实验将通过云拨测对指定服务器进行Ping/DNS监测,评估网站服务质量和用户体验。
目录
相关文章
|
17天前
|
jenkins 测试技术 持续交付
提升软件测试效率的创新实践
在软件开发过程中,测试环节扮演着至关重要的角色。本文探讨了如何通过创新的方法和工具,提高软件测试的效率和质量。我们将从自动化测试、持续集成与持续部署(CI/CD)、测试驱动开发(TDD)三个方面,详细介绍这些技术如何改变传统的测试流程,帮助团队更快地发现和修复缺陷,最终实现更高质量的软件交付。
137 67
|
15天前
|
前端开发 API 决策智能
多智能体微调实践:α-UMi 开源
近年来,为了加强大型语言模型(Large-Language Models, LLM)实时信息处理、解决专业问题的能力,催生了工具调用智能体(Tool Integrated Agent)概念
|
3天前
|
Java 测试技术 开发者
初学者入门:掌握单元测试的基础与实践
【10月更文挑战第14天】单元测试是一种软件测试方法,它验证软件中的最小可测试单元——通常是单独的函数或类——是否按预期工作。单元测试的目标是确保每个模块在其自身范围内正确无误地运行。这些测试应该独立于其他模块,并且应该能够反复执行而不受外部环境的影响。
15 2
|
11天前
|
机器学习/深度学习 人工智能 监控
提升软件质量的关键路径:高效测试策略与实践在软件开发的宇宙中,每一行代码都如同星辰般璀璨,而将这些星辰编织成星系的过程,则依赖于严谨而高效的测试策略。本文将引领读者探索软件测试的奥秘,揭示如何通过精心设计的测试方案,不仅提升软件的性能与稳定性,还能加速产品上市的步伐,最终实现质量与效率的双重飞跃。
在软件工程的浩瀚星海中,测试不仅是发现缺陷的放大镜,更是保障软件质量的坚固防线。本文旨在探讨一种高效且创新的软件测试策略框架,它融合了传统方法的精髓与现代技术的突破,旨在为软件开发团队提供一套系统化、可执行性强的测试指引。我们将从测试规划的起点出发,沿着测试设计、执行、反馈再到持续优化的轨迹,逐步展开论述。每一步都强调实用性与前瞻性相结合,确保测试活动能够紧跟软件开发的步伐,及时适应变化,有效应对各种挑战。
|
21天前
|
SQL 测试技术 持续交付
探索软件测试的多维度——从理论到实践
【9月更文挑战第35天】在软件工程的世界中,测试是一个不可或缺的环节。它不仅保障了软件产品的质量,而且确保了用户体验的一致性和可靠性。本文将从不同的角度切入,探讨软件测试的多个方面,包括测试的目的、类型、工具以及最佳实践。通过深入浅出的方式,我们旨在为读者提供一个全面的测试知识框架,帮助他们更好地理解并执行软件测试工作。
28 2
|
8天前
|
测试技术 UED
软件测试的艺术与实践
【10月更文挑战第9天】 在数字时代的浪潮中,软件成为了我们生活和工作不可或缺的一部分。然而,高质量的软件背后,是无数测试工程师的默默付出。本文将通过深入浅出的方式,探讨如何进行高效的软件测试,确保软件产品的质量与稳定性。我们将一起揭开软件测试的神秘面纱,从基础理论到实际操作,一步步走进这个充满挑战与创造的世界。
|
3天前
|
机器学习/深度学习 人工智能 自然语言处理
探索AI在软件测试中的创新应用与实践###
本文旨在探讨人工智能(AI)技术如何革新软件测试领域,提升测试效率、质量与覆盖范围。通过深入分析AI驱动的自动化测试工具、智能化缺陷预测模型及持续集成/持续部署(CI/CD)流程优化等关键方面,本研究揭示了AI技术在解决传统软件测试痛点中的潜力与价值。文章首先概述了软件测试的重要性和当前面临的挑战,随后详细介绍了AI技术在测试用例生成、执行、结果分析及维护中的应用实例,并展望了未来AI与软件测试深度融合的趋势,强调了技术伦理与质量控制的重要性。本文为软件开发与测试团队提供了关于如何有效利用AI技术提升测试效能的实践指南。 ###
|
12天前
|
测试技术
软件测试中的探索性测试(ET)实践
【10月更文挑战第5天】本文将深入探讨一种与传统脚本化测试不同的测试方法——探索性测试(Exploratory Testing,简称ET)。我们将通过一个实际案例来展示ET的有效性,并分享如何将ET融入日常的软件测试流程中。文章旨在为测试人员提供一种灵活、高效的测试策略,帮助他们更好地发现软件中的缺陷。
|
12天前
|
Web App开发 设计模式 测试技术
自动化测试框架的搭建与实践
【10月更文挑战第5天】本文将引导你理解自动化测试框架的重要性,并通过实际操作案例,展示如何从零开始搭建一个自动化测试框架。文章不仅涵盖理论,还提供具体的代码示例和操作步骤,确保读者能够获得实用技能,提升软件质量保障的效率和效果。
|
15天前
|
测试技术 持续交付 Python
软件测试中的自动化策略与实践
【10月更文挑战第2天】在软件开发的海洋中,自动化测试如同一座灯塔,为追求高效率和高质量的航程提供方向。本文将深入探讨自动化测试的策略与实践,从基础理论到实际应用,带领读者领略自动化测试的魅力和挑战。