Spock 测试框架

Spock 测试框架

1. Spock Primer

todo

2. 数据驱动测试

todo

3. 基于交互的测试

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

interface Subscriber {
  void receive(String message)
}

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

mock 框架提供了一种方式, 可以描述 spec 对象(即待测对象)及其协作者之间的期望交互,
并且能生成协作者的 mock 实现, 用来核查这个期望.

要测试 send 方法, 得有 Subscriber 类型的协作者.

3.1 创建 mock 对象

创建两个 mock 对象:

def subscriber = Mock(Subscriber)
def subscriber2 = Mock(Subscriber)
// 用下面的方式也是可以的, ide 自动提示功能可能会更好:
// mock 对象的类型会根据变量类型推断出来
Subscriber subscriber = Mock()
Subscriber subscriber2 = Mock()

3.2 把 mock 对象注入到待测对象

class PublisherSpec extends Specification {
  Publisher publisher = new Publisher()
  Subscriber subscriber = Mock()
  Subscriber subscriber2 = Mock()

  def setup() {
    publisher.subscribers << subscriber // << is a Groovy shorthand for List.add()
    publisher.subscribers << subscriber2
  }

3.3 Mocking

3.3.1 Interactions

then: block 中, 有两个 interactions, 每个都由 cardinality, target, method, argument
组成.

1 * subscriber.receive("hello")
|   |          |       |
|   |          |       argument constraint
|   |          method constraint
|   target constraint
cardinality

3.3.2 Cardinality(基数)

一个交互(interaction) 的基数描述了期望一个方法调用几次. 它可以是一个固定数字, 也
可以是范围.


1 * subscriber.receive("hello")      // 精确的 1 次调用
0 * subscriber.receive("hello")      // 0 次
(1..3) * subscriber.receive("hello") // 1 到 3 次 (inclusive)
(1.._) * subscriber.receive("hello") // 至少 1 次
(_..3) * subscriber.receive("hello") // 最多 3 次
_ * subscriber.receive("hello")      // any number of calls, including zero
                                     // (rarely needed; see 'Strict Mocking')

3.3.3 Target Constraint

Target constraint 描述了期望哪个 mock 对象来接收方法调用.


1 * subscriber.receive("hello") // a call to 'subscriber'
1 * _.receive("hello")          // a call to any mock object

3.3.4 Method Constraint

Method constraint 描述了期望哪个方法被调用


1 * subscriber.receive("hello") // a method named 'receive'
1 * subscriber./r.*e/("hello")  // a method whose name matches the given regular expression

当期望一个 getter 方法调用时, 可以用 groovy 的属性访问语法.


1 * subscriber.status // same as: 1 * subscriber.getStatus()

当期望一个 setter 方法调用时, 只能使用方法语法.


1 * subscriber.setStatus("ok") // NOT: 1 * subscriber.status = "ok"

3.3.5 Argument Constraints

描述了方法期望的参数.


1 * subscriber.receive(!"hello")    // an argument that is unequal to the String "hello"
1 * subscriber.receive()            // the empty argument list (would never match in our example)
1 * subscriber.receive(_)           // any single argument (including null)
1 * subscriber.receive(*_)          // any argument list (including the empty argument list)
1 * subscriber.receive(!null)       // any non-null argument
1 * subscriber.receive(_ as String) // any non-null argument that is-a String
1 * subscriber.receive({ it.size() > 3 }) // an argument that satisfies the given predicate
                                          // (here: message length is greater than 3)

当然, 多个参数也是可以的.

处理可变参数时, 跟平时一样用就可以了.

interface VarArgSubscriber {
    void receive(String... messages)
}

...

subscriber.receive("hello", "goodbye")

3.3.6 匹配任意方法调用


1 * subscriber._(*_)     // any method on subscriber, with any argument list
1 * subscriber._         // shortcut for and preferred over the above

1 * _._                  // any method call on any mock object
1 * _                    // shortcut for and preferred over the above

3.3.7 Strick Mocking(严格模式)

3.3.8 Where to Declare Interactions

Interactions 不只是可以在 then: block 中出现, 也可以在 setup 方法中出现.

3.3.9 Declaring Interactions at Mock Creation Time (New in 0.7)

如果一个 mock 对象有一组基本的不变的 interactions. 这些 interactions 可以在 mock
对象创建时就声明.

def subscriber = Mock(Subscriber) {
    1 * receive("hello")
    1 * receive("goodbye")
}

// 也可以这样
Subscriber subscriber = Mock {
    1 * receive("hello")
    1 * receive("goodbye")
}

这个特性对 带有专门的 Stubs 的 Stubbing 很有用. 注意上面的 interactions 没有 target
contraints, 因为从上下文中可以清楚的知道他们属于哪个 mock 对象.

3.3.10 Grouping Interactions with Same Target (New in 0.7)

with(subscriber) {
    1 * receive("hello")
    1 * receive("goodbye")
}

一个 with block 还可以用在 grouping conditions.

3.3.11 Mixing Interactions and Conditions

一个 then: block 可以同时包含 interactions 和 conditions. 尽管不是严格必须, 但是
通常的做法是把 interactions 的声明放在 conditions 前面.


when:
publisher.send("hello")

then:
1 * subscriber.receive("hello")
publisher.messageCount == 1

3.3.12 显式定义 interaction 代码块

Internally, Spock must have full information about expected interactions before they take place. So how is it possible for interactions to be declared in a then: block? The answer is that under the hood, Spock moves interactions declared in a then: block to immediately before the preceding when: block. In most cases this works out just fine, but sometimes it can lead to problems:

这样做会出问题

when:
publisher.send("hello")

then:
def message = "hello"
1 * subscriber.receive(message)

Spock 不知道这个交互链接到一个变量声明. 只会把交互这一条语句移到 when: 代码块里去.
所以会产生 MissingPropertyException 异常.

一个解决办法是把 message 声明移到 when: 代码块之前(或者放在 where 代码块中).

另一个解决办法是显式的声明 interaction 代码块

when:
publisher.send("hello")

then:
interaction {
    def message = "hello"
    1 * subscriber.receive(message)
}

3.3.13 Scope of Interactions

3.3.14 Verification of Interactions

主要是基数不匹配

3.3.15 Invocation Order

在一个 then: block 中, invocation 的顺序是不重要的.

例如:

then:
2 * subscriber.receive("hello")
1 * subscriber.receive("goodbye")

可以匹配 "hello" "hello" "goodbye", "hello" "goodbye" "hello", 都没问题.

如果要严格限定顺序的情况, 可以写在两个 then: block 中.

then:
2 * subscriber.receive("hello")

then:
1 * subscriber.receive("goodbye")

3.3.16 Mocking Classes

除了接口, spock 还支持类的 mock. 基本与接口的 mock 是一致的, 除了需要额外的 cglib
库支持. cglib-nodep-2.2 or higher and objenesis-1.2 or higher.

如果 classpath 中没有, 会报错哦. Spock 会告诉你这种错误信息的.

org.spockframework.mock.CannotCreateMockException: Cannot create mock for class cn.codergege.demo.mock.NewSubcriber. Mocking of non-interface types requires a code generation library. Please put byte-buddy-1.4.0 or cglib-nodep-3.2 or higher on the class path.

注意: java 8 只支持 CGLIB 3.2.0 及更高版本.

3.4 Stubbing

todo

3.5 Combining Mocking and Stubbing


1 * subscriber.receive("message1") >> "ok"
1 * subscriber.receive("message2") >> "fail"

当 mocking 和 stubbing 同一个方法时, 他们必须出现在同一个 interaction 中. 下面这
种 Mockito 风格的写法是不能工作的(分开 stubbing 和 mocking 到不同的语句中)


setup:
subscriber.receive("message1") >> "ok"

when:
publisher.send("message1")

then:
1 * subscriber.receive("message1")

这个 receive 方法将在 then: 代码块中第一次匹配. 因为此时没有指定返回值, 所以默认
的方法返回值被返回(这里会返回 null). 因此, 在 setup: 代码块中的 interaction 永远
没有匹配的机会.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容

  • 1.Creating mock objects 1.1Class mocks idclassMock=OCMCla...
    奔跑的小小鱼阅读 2,529评论 0 0
  • 2017090 NO.38 用时37分钟1012字 和J又聊了75分钟,放下电话,早已熄灯久矣。 这场谈话,从生活...
    雨欲余阅读 486评论 0 2
  • 昨天,薛之谦在上海演唱会上赴十年之约为前妻弹琴演唱《安和桥》,他说: “现场有一个人,她没有联系过我,但我觉得她...
    图文不符阅读 271评论 0 2
  • 之前看了一篇帖子,好像叫《张小龙饭否2XXX条日记……》,一直想看,疲于工作忘记了,今天无意中又看到了这个帖子,就...
    weid阅读 218评论 0 0
  • 咦,星子在闪 眼里有光在流转 说要变成夜莺唱歌给你听 却在心里为你跳起舞蹈 多么久远的爱着你 比过月亮比过太阳比过...
    小倩星阅读 388评论 0 61