IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 开发测试 -> Spock测试框架浅尝 -> 正文阅读

[开发测试]Spock测试框架浅尝


在这里插入图片描述

0. 写在前面

这篇文章会介绍 Spock 测试框架相关的一些基本概念,包括数据驱动测试(Data Driven Testing)、基于交互的测试(Interaction Based Testing)、Mocking 和 Stubbing 等,这其中会带有一些简单的例子。

这篇文章绝大部分内容来自Spock官方文档,说它为官方文档的粗糙翻译也不为过🥲。我把自己使用 Spock 时认为比较重要的内容放在了这里,它们只是概念性的解释,参考资料 - 2是一篇快速上手文档,我也会尽量在下篇文章中展示工程中实际使用 Spock 的场景。如果哪些地方描述的太过晦涩,你可以直接移步至官方文档。

文章主要会从单元测试的角度介绍 Spock,对于集成测试,使用方式不会相差很多。还有你可能需要懂一点 Groovy 语言,如果例子中的某些地方你看起来有些吃力,它们很可能是 Groovy 的特性。现在,让我们开始吧!

1. 为什么要进行单元测试?

你可能很随意地说出单元测试的一些好处,不过我们不妨再复习一遍,你会对单元测试有新的体会。

1.1 什么是单元测试?

单元测试(Unit Testing)是一种软件测试方法,它会测试源代码中的各个单元来确定它们是否适合使用。单元是指最小的可测试软件组件,通常是一个只包括单一功能的方法。相比于更大的代码块,记录和分析测试结果更容易,单元测试揭示的缺陷更容易定位,也相对更容易修复,简单讲,单元测试更容易进行。

1.2 单元测试的几个好处

  1. 提高代码质量、尽早发现缺陷和降低后期修复成本:软件工程中提到过,越早的暴露缺陷,修复的成本越低,单元测试应该算是发现代码bug的最早过程。同时,这也会间接地提高代码质量,你可能还会思考有没有更好的代码设计,并考虑测试单元的核心功能,因为你需要保证它可测试;
  2. 开发过程会更敏捷、有利于安全重构:当你向工程中添加越来越多的功能时,有时需要修改旧的设计和代码。然而,更改测试过的代码既危险又昂贵。如果我们有单元测试,你就可以很自信地进行重构,及时地确保重构后的代码没有问题;
  3. 方便调试、对所写代码更有体感:毫无疑问,单元测试更容易调试,当进行单元测试时,我们的目光会聚焦于单个单元,你不必去考虑依赖和上下文。更重要的是,对于无法在本地启动的较大工程,单元测试能让你在本地执行和调试,这会具体到你需要考虑单元中的每个数据类型和边缘条件。

2. Spock框架介绍

我们首先来看 Spock 官方文档对自己的定义:

Spock is a testing and specification framework for Java and Groovy applications. …Thanks to its JUnit runner, Spock is compatible with most IDEs, build tools, and continuous integration servers.

和某篇优秀的guide提及的内容:

Mainly, Spock aims to be a more powerful alternative to the traditional JUnit stack, by leveraging Groovy features.

首先,Spock 是用于 Java 和 Groovy 应用程序的测试和规范框架。因为测试代码需要用 Groovy 来写,所以你可以用到 Groovy 的一些灵活的特性,并且 Spock 还有自己的一些很酷的语法。因为其底层依赖 JUnit,所以你不必担心 JUnit 完成的测试 Spock 做不到,而且 Spock 会让测试代码更简洁。我们接着来看与 Spock 有关的一些概念。

2.1 规范 Specification

我们先来看官方文档中的一段介绍:

Spock lets you write specifications that describe expected features (properties, aspects) exhibited by a system of interest.

我们重点说明下划线标记的3个地方。首先,这里 Specification 是指软件需求规范(Software Requirements Specification),它完整描述了期望某个系统怎样运行。我们会使用 Spock 编写 Specification,它会描述一个兴趣系统(System Of Interest,我们这里简称为 SOI ) 的预期 features。SOI 可以是一个简单类或整个应用程序,这主要看你想测试什么,比如想做集成测试或系统测试,SOI 的范围就会相应变大;如果是单元测试,把范围限制到单个类即可。接着 features 就是我们想测试的一个个功能点,后面还会提到它。

class HelloSpecification extends Specification {
    // fields
    // fixture methods <--
    // feature methods <--
    // helper methods
}

我们实现的 specification 是一个继承自spock.lang.Specification的 Groovy 类,类Specification包含很多我们编写规范时有用的方法。命名时通常和 SOI 关联,比如PlanetServiceSpecCustomerSpec

夹具方法 Fixture Methods

def setupSpec() {}    // runs once -  before the first feature method
def setup() {}        // runs before every feature method
def cleanup() {}      // runs after every feature method
def cleanupSpec() {}  // runs once -  after the last feature method

Fixture methods 负责初始化和清理运行 feature methods 的环境。你可以将它类比到 JUnit 中的@Before@After。这些夹具方法会有一定的运行顺序,更详细说明你可以参考官方文档:Fixture Methods

特征方法 Feature Methods

def "one plus one should equal two"() {
	// blocks go here, e.g.
    expect:
    1 + 1 == 2
}

特征方法是整个 Spock 的核心,现在把目光聚焦到 feature,我们做测试的根本目的是关注某个 feature(或者叫功能/特征)是否可行。实际上,Spock 中的 feature 就是 JUnit 中的 test。你会发现特征方法的方法名可以是字符串,所以你应该起个好名字,来说明这个方法主要做什么测试。

相比于 JUnit,Spock 将一个特征方法拆分为表达更形象的4个阶段(phases):

  1. Setup:初始化方法运行环境(可选)
  2. Stimulus:就像它的名字,被测试方法启动的地方
  3. Response:验证测试结果,就像 JUnit 中的断言
  4. Cleanup:清理方法运行环境(可选)

块 Blocks

在这里插入图片描述
Spock 提供了块(blocks)去支持这些阶段,你可以仔细看上面的图,Blocks 是 Spock使用标签来拆分测试阶段的一种原生方式,它们是givenwhenthenexpectcleanupwhere,其中where比较特殊,它没有对应特殊的阶段,而是会和整个特征方法运行时关联。你可以在官方文档:Feature Methods 找到更详细的说明。

我们顺带使用 Groovy 的一些特性,比如:

def "element should be able to remove from list"() {
    given:
    def list = [1, 2, 3, 4]

    when:
    list.remove(0)

    then:
    list == [1, 3, 4]
}

我们在given中提供相关的环境,在when中给出条件,最后在then中判断。通常,givenwhenthen这样的三段式会很常见。

同时,你会发现上面那个特征方法运行应该是失败的,不过,测试失败时的结果展示也很清晰:

Condition not satisfied:

list == [1, 3, 4]
|    |
|    false
[2, 3, 4]
 <Click to see difference>

at FirstSpecification.element should be able to remove from list(FirstSpecification.groovy:30)

2.2 数据驱动测试 Data Driven Testing

可以说,数据驱动测试是使用不同的参数和断言多次测试相同的行为(behavior)。使用 Spock 会让这种做法操作起来更方便。

数据表格 Data Tables

使用数学例子比较能清晰地说明这种情况:

class MathSpec extends Specification {
    def "maximum of two numbers"() {
        expect:
        Math.max(a, b) == c

        where:
        a | b | c
        1 | 3 | 3
        7 | 4 | 7
        0 | 0 | 0
    }
}

Spock 提供了一种更方便的方式进行参数化测试(parameterized test),那就是数据表格(Data Table),同时它应该在where块中,表格中的值可以是多种类型。

数据管道 Data Pipes

你会发现given块中设置固定的环境,where块中会提供流动的数据。有时,数据表格提供数据的方式并不是那么灵活,比如数据类型有点复杂,或者数据来自外部函数,像是测试用的数据库。其实,数据表格不是 Spock 提供数据的唯一方式,并且它其实是一个或多个数据管道(Data Pipe)组合后的语法糖

...
where:
a << [1, 7, 0]
b << [3, 4, 0]
c << [3, 7, 0]

这种写法和上面数据表格写法所表达的意思是一样的。数据管道会更灵活一些,比如存在嵌套的数据结构:

...
where:
[a, [b, _, c]] << [
    ['a1', 'a2'].permutations(),
    [
        ['b1', 'd1', 'c1'],
        ['b2', 'd2', 'c2']
    ]
].combinations()

它对应下方的数据表格,_表示该字段无意义。

abc
[‘a1’, ‘a2’]‘b1’‘c1’
[‘a2’, ‘a1’]‘b1’‘c1’
[‘a1’, ‘a2’]‘b2’‘c2’
[‘a2’, ‘a1’]‘b2’‘c2’

另外,where块还可以进行变量赋值(Variable Assignments),我们可以把数据表格、数据管道和变量赋值同时放在where块中。

2.3 基于交互测试 Interaction Based Testing

基于交互测试是在极限编程(XP)中出现的一种设计和测试技术,它更加关注对象的行为(behavior)而不是其状态。基于此,我们测试时考虑点可以是规范下的对象(object under specification)怎样通过方法调用的形式和其关联对象交互。

我们来看一个例子,假使我们有一个Publisher,它能向订阅自己的每个Subscriber发送消息:

class Publisher {
    List<Subscriber> subscribers = []
    int messageCount = 0
    void send(String message) {
        subscribers*.receive(message)
        messageCount++
    }
}

interface Subscriber {
    void receive(String message)
}

class PublisherSpec extends Specification {
    Publisher publisher = new Publisher()
}

我们非常想确定的一点是:Publisher发送消息后,它的Subscriber是否能收到消息,并且收到的消息和发送的消息一致。为了达到这个目的,我们需要一个特殊的Subscriber来监听PublisherSubscriber之间的交互行为,这个特殊的Subscriber在 Spock 中被称为 mock object

在 Spock 中创建 mock 对象很容易,可以调用MockingApi.Mock()方法:

def subscriber = Mock(Subscriber)
def subscriber2 = Mock(Subscriber)
// 另外一种写法
Subscriber subscriber = Mock()
Subscriber subscriber2 = Mock()

对于 mock 对象,有两个非常重要的概念,分别是 MockingStubbing。我们现在来看一看:

Mocking

Mocking is the act of describing (mandatory) interactions between the object under specification and its collaborators.

上方引用是 mocking 的官方定义,mocking 描述测试对象和其关联对象之间准确的交互行为。拿开始时的Publisher为例:

class PublisherSpec extends Specification {

    Publisher publisher = new Publisher()

    def setup() {
    }

    def "subscribers should receive the message"() {
        given:
        Subscriber subscriber = Mock()
        Subscriber subscriber2 = Mock()
        publisher.subscribers << subscriber
        publisher.subscribers << subscriber2

        when:
        publisher.send("Hello")

        then:
        1 * subscriber.receive("Hello")  // <-- 看这里
        1 * subscriber2.receive("Hello")
    }
}

这个唯一的 feature method 表达的意思是:当发布者发送 “Hello” 消息时,两个订阅者都应该只收到一次该消息。我尝试通俗地解释一下 mocking:当测试对象(object of specification)运行某个方法(stimulus)时,对应地,方法内涉及的对象(collaborator)应该出现什么样的行为,mocking 使我们能够准确的验证这些行为。

我们仔细看一下then块中的一个交互:

1 * subscriber.receive("Hello")
|   |          |       |
|   |          |       argument constraint
|   |          method constraint
|   target constraint
基数 cardinality

利用这四个约束,我们能做到准确约束某个行为。更加详细的说明,请参考官方文档:Constraint

Stubbing

Stubbing is the act of making collaborators respond to method calls in a certain way.

有时,对于某个 mock 对象,我们希望模拟它的行为,也就是说:当 mock 对象的一个方法调用时,它会返回一个模拟的结果,而不是方法真正执行后的返回结果。这也就是 stubbing 的含义,就像上方引用中官方对 stubbing 的定义。

我们来看一个例子:

// Planet.java
public class Planet {
	private String name;
}

// xxSpec.groovy
...
given:
def earth = Mock(Planet)
earth.getName() >> "Earth"  // <-- 看这里

earth是我们的 mock 对象,我们没有设置其name属性的值,但是我们可以通过 stubbing 的方式伪造name,就是earth.getName() >> "Earth"

另外,stubbing 可以在when块之前的任何块内。

3. 小结

在这篇文章中,我们首先说明了单元测试的必要性,然后我们介绍 Spock 框架,我们能够根据测试的范围来确定规范下的系统(System Under Specification),Specification 会包含几个部分,其中 fixture methods 可以设置和清理整个 specification 的环境,所以每个 feature method 是在其之中运行的。对于 feature methods,phases 和 blocks 是很重要的概念,一个 feature method 被划分为职责清晰的 block。

使用 Spock,我们能更轻松地伪造测试数据,也就是说,数据驱动测试更容易。Spock 会更关注测试对象的行为(behavior),mocking 验证行为交互是否合理,而 stubbing 可以伪造关联对象方法的返回结果。

最后,祝你好运。

参考资料

  1. Spock: Spock Framework Reference Documentation
  2. Baeldung: Introduction to Testing with Spock and Groovy
  3. DZone: 8 Benefits of Unit Testing
  4. SoftwareTestingHelp: How To Test JAVA Applications?
  开发测试 最新文章
pytest系列——allure之生成测试报告(Wind
某大厂软件测试岗一面笔试题+二面问答题面试
iperf 学习笔记
关于Python中使用selenium八大定位方法
【软件测试】为什么提升不了?8年测试总结再
软件测试复习
PHP笔记-Smarty模板引擎的使用
C++Test使用入门
【Java】单元测试
Net core 3.x 获取客户端地址
上一篇文章      下一篇文章      查看所有文章
加:2021-12-07 12:20:35  更:2021-12-07 12:21:40 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/18 6:42:35-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码