用junit5编写一个类ZeroCode的测试框

技术点:

最近笔者在尝试基于应用日志来自动生成测试用例。这其中就需要一个配套的简易测试框架。梳理了一下,其中的技术点有: 0.使用csv文件来定义测试用例及步骤 1.使用自定义测试注解来定义测试用例(参考ZeroCode) 2.使用Junit5提供的extension机制来实现测试执行 3.使用简单工厂类提供执行驱动 4.使用OpenCsv来实现解析 5.使用Lombok来定义Java Bean 6.使用Junit5提供的参数化测试解决方案junit-jupiter-params来实现测试用例集

来自ZeroCode的参考

ZeroCode是一个轻量级的开源测试框架。它通过使用JSON或者YAML文件格式来定义测试用例,进而让测试用例的编写变得更为容易。 以下是其github项目首页提供的案例

   @Test
   @Scenario("test_customer_get_api.yml")
   public void getCustomer_happyCase(){
        // No code goes here. This remains empty.
   }

其中test_customer_get_api.yml中就描述了这个接口测试用例的全部要素,具体如下:

---
url: api/v1/customers/123
method: GET
request:
  headers:
    Content-Type: application/json
retry:
  max: 3
  delay: 1000
verify:
  status: 200
  headers:
    Content-Type:
    - application/json; charset=utf-8
  body:
    id: 123
    type: Premium Visa
    addresses:
    - type: Billing
      line1: 10 Random St
verifyMode: LINIENT

这个YAML文件中包括了http接口测试中所需要的请求(含url、head、类型)以及返回、验证模式等内容,是一个不错的用例DSL。本身这是一个很好的开源测试框架,涵盖的测试类型也比较多,参与维护的人员和更新速度也不错。

A community-developed, free, open source, declarative API automation and load testing framework built using Java JUnit core runners for Http REST, SOAP, Security, Database, Kafka and much more. It enables to create and maintain test-cases with absolute ease.

实际项目中的需求

在实际的测试过程中,对于文本格式的测试用例,往往有以下的需求:

  1. 测试用例的步骤描述通常是自定义的,而不是根据工具提供的DSL来编写。
  2. 用例编写尽可能少一些冗余的内容,以便节约用例编写时间。例如在前述接口测试案例中的head,Content-Type等等,在某个系统的接口规范中,往往都是规定了固定格式的。
  3. 可能的话,@Test之类的Java代码也不用写了。测试人员只写用例文件,框架通过扫码文件目录和文件来执行用例。

为了实现上述需求,这就要求根据测试的特点,来定制一个类似的简易测试框架。

使用文件来定义测试用例和步骤

当设计一个自动化测试用例框架时,有一个很重要的三联问问题:

如何定义一个用例?如何定义用例的步骤?如何定义一个用例集?

在本案例中,我们约定

  • 一个文件(csv)是一个用例
  • 文件中的一行是用例的一个步骤
  • 包含若干文件的目录,组成了一个用例集

至于用csv文件来作为用例的载体,而不是json/yaml等更新的文件类型,或者xml/excel等传统文件类型,主要是基于以下两方面考虑

  • 接口自动化测试,尤其是面向业务功能的测试,其请求、入参、出参的结构相对固定。
  • csv擅长表达结构固定的数据内容,且格式冗余最小。

因此,如果以前述ZeroCode的接口为例,一个简单的接口自动化测试的用例格式可以是

num

type

url

params

response

1

get

“api/v1/customers/123”

{"id": 123,"type": "Premium High Value","addresses": [{"type":"home","line1":"10 Random St"}]}"

读者可能会问,那么head,content-type这些不要了么?status code 都等于200么?实际项目中经常用到的token怎么没有体现?等等问题。 这里我们假设,

  • head,content-type,token这些内容在功能测试时,基本属于不变化的内容。
  • status code =200,如果有业务层面的错误,在response中可以通过errorCode的方式体现。 每个框架都有其应用场合。上面这样的框架,主要是应用于业务层面的测试,而不是接口自身的鲁棒性测试等场合。这样简单的格式,也非常适合不太能写代码的同学来写自动化测试用例。

以下是编写完成以后的一个测试用例的样例

package org.codefx.demo.junit5.extensions;
import org.junit.jupiter.api.Test;
import com.demo.junit5.Scenario;

class ScenarioTest {
    @Test
    @Scenario(value=".\\tests\\demo1\\sample.csv")
    public void sampleTest()   {
    }

}

其中的sample.csv中的内容就是前述表格中的内容

[未完待续]

自定义注解

我们来看下测试用例中的注解的具体实现:

package com.demo.junit5;
import java.lang.annotation.*;
import org.junit.jupiter.api.extension.ExtendWith;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(ScenarioExtension.class)
public @interface Scenario {
    String[] value() default "";
}

通过@Scenario 在某个方法上的注解,可以调用 @ExtendWith(ScenarioExtension.class) 中的具体功能。这也是JUnit5提供的一种回调机制,来扩展Junit5测试框架的功能。

具体的Extension

JUnit5提供了非常友好的扩展性,最常用的是Before/After配套的一些Callback接口上,如下图所示:

这里我们就使用了一个BeforeTestExecutionCallback的接口来进行扩展,在被注解的用例执行之前,Junit5会首先调用该接口,实现自定义的功能。

package com.demo.junit5;

import java.io.IOException;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Iterator;

import org.junit.jupiter.api.extension.BeforeTestExecutionCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.platform.commons.util.AnnotationUtils;
import com.demo.junit5.bean.TestStep;
import com.demo.junit5.runner.Runner;
import com.demo.junit5.runner.RunnerFactory;
import com.opencsv.bean.CsvToBean;
import com.opencsv.bean.CsvToBeanBuilder;
class ScenarioExtension
        implements BeforeTestExecutionCallback {
    private Runner runner = RunnerFactory.getRunner("");
       //实际项目中一般通过配置来传入具体的runner类型。这里只是一个Dummy样例。
    // EXTENSION POINTS
    public void beforeTestExecution(ExtensionContext context) 
throws Exception{
        Scenario scenario = AnnotationUtils.findAnnotation(
context.getRequiredTestMethod(), Scenario.class)
.orElse(null);
        if (scenario == null) {
            scenario = AnnotationUtils.findAnnotation(
context.getRequiredTestClass(), Scenario.class)
.orElse(null);
        }
        for(String v:scenario.value()) {
        runCase(runner,v);
        }
    }
    private static void runCase(Runner runner,String testCase) 
throws IOException {
Reader reader = Files.newBufferedReader(Paths.get(testCase));
CsvToBean<TestStep> csvToBean = new CsvToBeanBuilder<TestStep>(reader)
            .withType(TestStep.class)
            .withIgnoreLeadingWhiteSpace(true)
            .withSeparator(',')
            .build();
            Iterator<TestStep> csvIterator = csvToBean.iterator();
            while(csvIterator.hasNext()) {
                TestStep testStep = csvIterator.next();
                runner.run(testStep);
            }
        
    }

}

通过实现BeforeTestExecutionCallback 接口中的beforeTestExecution方法,可以将传入的用例文件内容(测试步骤)进行解析,并交给一个Runner进行执行。

执行器Runner相关

再来看一下Runner接口

package com.demo.junit5.runner;
import com.demo.junit5.bean.TestStep;
public interface Runner {
           public void run(TestStep testStep);
}

其中的run接口用于具体的执行。作为示例,这里先给一个MockRunner

package com.demo.junit5.runner;

import com.alibaba.fastjson.JSON;
import static org.assertj.core.api.Assertions.assertThat;
import com.demo.junit5.bean.TestStep;

import lombok.extern.slf4j.Slf4j;
@Slf4j
public class MockRunner implements Runner {
    public void run(TestStep testStep) {
        log.info("mock runner called");
        log.info("step #{}",testStep.getNum());
        log.info(JSON.toJSONString(testStep));
ssertThat(testStep.getResponse()).isEqualToIgnoringCase(testStep.getResponse());
    }
}

实际工作中可以使用Rest-Assured等工具来实现HTTP接口的调用,并进行结果的验证。如果是TCP等类型的接口,换一种具体实现即可。 有经验的读者可能已经想到了,这就是一个典型的工厂设计模式的使用场景。我们用一个简单工厂作为示例:

package com.demo.junit5.runner;
public class RunnerFactory {
        public static Runner getRunner(String runner) {
            return new MockRunner();
        }
}

目前这个工厂只提供MockRunner一种实现。

业务Bean -TestStep

测试步骤的Bean 如下:

package com.demo.junit5.bean;
import com.opencsv.bean.CsvBindByName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TestStep {
    @CsvBindByName(column="num")
    private int num;
    @CsvBindByName(column="type")
    private String type;
    @CsvBindByName(column="url")
    private String url;
    @CsvBindByName(column="params")
    private String params;
    @CsvBindByName(column="response")
    private String response;
}

通过Lombok极大地简化了代码。而通过opencsv,可以极为方便地实现csv文件和bean之间地转换。

CsvToBean<TestStep> csvToBean = new CsvToBeanBuilder<TestStep>(reader)
        .withType(TestStep.class)
        .withIgnoreLeadingWhiteSpace(true)
        .withSeparator(',')
        .build();

只要通过opencsv5.0提供的建造者方法一行代码就能完成了。

小节

至此,一个简单的自定义文件的测试框架就构建完毕了,从测试用例来看,测试方法体可以是ZeroCode,基本实现了全部测试用例在文件中体现的目标。总结一下使用到的技术点:

0.使用csv文件来定义测试用例及步骤 1.使用自定义测试注解来定义测试用例(参考ZeroCode) 2.使用Junit5提供的extension机制来实现测试执行 3.使用简单工厂类提供执行驱动 4.使用OpenCsv来实现解析 5.使用Lombok来定义Java Bean

至于参数化构建,我们将在后续完成。

本文分享自微信公众号 - V社 北京社(SoftwareTesters)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-11-11

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏芋道源码1024

一个 Java 对象到底有多大?

编写Java代码的时候,大多数情况下,我们很少关注一个Java对象究竟有多大(占据多少内存),更多的是关注业务与逻辑。但是殊不知,在我们不经意间,大量的内存被无...

10410
来自专栏丑胖侠

面试官,Java8 JVM内存结构变了,永久代到元空间

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 ...

10660
来自专栏Java识堂

帮你少写一大半参数校验代码的小技巧

几乎每个web网站都会对用户提交的参数进行校验,前端要做,后端也要做。防止用户直接通过接口调用的方式来请求或保存数据,从而导致产生脏数据等其他严重的后果。

10520
来自专栏搜云库技术团队

常问的15个顶级Java多线程面试题

在任何Java面试当中多线程和并发方面的问题都是必不可少的一部分。如果你想获得更多职位,那么你应该准备很多关于多线程的问题。

10530
来自专栏Crossin的编程教室

【Python 第75课】可迭代对象和迭代器

for 循环是我们在 Python 里非常常用的一个语法,但你有没有思考过 for 循环是怎样实现的?

10520
来自专栏北京宏哥

Java自动化测试框架-04 - TestNG之Test Method篇 - 道法自然,法力无边(详细教程)

测试方法是可以带有参数的。每个测试方法都可以带有任意数量的参数,并且可以通过使用TestNG的@Parameters向方法传递正确的参数。

13120
来自专栏码洞

每个阿里程序员都必须搞懂的Maven基础知识

以前我们写代码时,jar包都默认放在一个叫 /lib 的目录下,然后把该目录设置为classpath可以读取到的目录,如下图所示:

9320
来自专栏业余草

面试再问ThreadLocal,别说你不会

以前面试的时候问到ThreadLocal总是一脸懵逼,只知道有这个哥们,不了解他是用来做什么的,更不清楚他的原理了。表面上看他是和多线程,线程同步有关的一个工具...

6910
来自专栏JAVA杂谈

Java日志Log4j或者Logback的NDC和MDC功能

Java中使用的日志的实现框架有很多种,常用的log4j和logback以及java.util.logging,而log4j是apache实现的一个开源日志组件...

15220
来自专栏Java3y

慌了!面试官又拿JVM开怼!

对于Java人来说,JVM无疑是进阶时必须迈过的坎。不管初入职场还是跳槽升职,JVM更是面试时的必考题。如果不懂JVM的话,薪酬会非常吃亏(近70%的面试者挂在...

8440

扫码关注云+社区

领取腾讯云代金券

年度创作总结 领取年终奖励