1. 概述
JUnit 和 TestNG 这类测试框架提供了基础的断言方法(如 assertTrue
、assertNotNull
)。而 Hamcrest、AssertJ 和 Truth 这类断言框架则提供了更丰富的流式断言方法,通常以 assertThat
开头。
JSpec 是另一个断言框架,它允许我们用接近自然语言规范的方式编写流式断言,尽管风格与其他框架略有不同。
本文将介绍 JSpec 的使用方法,包括编写规范所需的方法以及测试失败时的错误信息。
2. Maven 依赖
添加 javalite-common
依赖(包含 JSpec):
<dependency>
<groupId>org.javalite</groupId>
<artifactId>javalite-common</artifactId>
<version>1.4.13</version>
</dependency>
最新版本请查看 Maven 中央仓库。
3. 断言风格对比
传统断言基于规则,而 JSpec 直接描述行为规范。快速对比 JUnit、AssertJ 和 JSpec 的相等性断言:
JUnit 写法:
assertEquals(1 + 1, 2);
AssertJ 写法:
assertThat(1 + 1).isEqualTo(2);
JSpec 写法:
$(1 + 1).shouldEqual(2);
JSpec 采用流式断言风格,但省略了 assert
/assertThat
前缀,改用 should
。这种写法更贴近真实规范描述,促进 TDD/BDD 实践。
看这个自然语言风格的示例:
String message = "Welcome to JSpec demo";
the(message).shouldNotBe("empty");
the(message).shouldContain("JSpec");
4. 规范的结构
规范语句包含两部分:期望创建器和期望方法。
4.1. 期望创建器
通过静态导入方法生成 Expectation
对象:a()
、the()
、it()
、$()
:
$(1 + 2).shouldEqual(3);
a(1 + 2).shouldEqual(3);
the(1 + 2).shouldEqual(3);
it(1 + 2).shouldEqual(3);
这些方法本质相同,仅提供不同表达方式。唯一区别:it()
是类型安全的,只允许同类型对象比较:
it(1 + 2).shouldEqual("3"); // 编译错误
4.2. 期望方法
规范语句的第二部分是期望方法(如 shouldEqual
、shouldContain
),描述具体规范要求。测试失败时抛出 javalite.test.jspec.TestException
异常,附带描述性错误信息(后续章节会展示)。
5. 内置期望
JSpec 提供多种期望方法,以下是各类别及失败场景示例:
5.1. 相等性期望
✅ shouldEqual()
, shouldBeEqual()
, shouldNotBeEqual()
使用 Object.equals()
判断对象是否相等:
$(1 + 2).shouldEqual(3);
❌ 失败场景:
$(1 + 2).shouldEqual(4);
错误信息:
Test object:java.lang.Integer == <3>
and expected java.lang.Integer == <4>
are not equal, but they should be.
5.2. 布尔属性期望
✅ shouldHave()
, shouldNotHave()
指定对象的布尔属性是否应返回 true
:
Cage cage = new Cage();
cage.put(tomCat, boltDog);
the(cage).shouldHave("animals");
要求 Cage
类包含方法:
boolean hasAnimals() {...}
❌ 失败场景:
the(cage).shouldNotHave("animals");
错误信息:
Method: hasAnimals should return false, but returned true
✅ shouldBe()
, shouldNotBe()
指定对象是否应满足某状态:
the(cage).shouldNotBe("empty");
要求 Cage
类包含方法:
boolean isEmpty() {...}
❌ 失败场景:
the(cage).shouldBe("empty");
错误信息:
Method: isEmpty should return true, but returned false
5.3. 类型期望
✅ shouldBeType()
, shouldBeA()
指定对象类型:
cage.put(boltDog);
Animal releasedAnimal = cage.release(boltDog);
the(releasedAnimal).shouldBeA(Dog.class);
❌ 失败场景:
the(releasedAnimal).shouldBeA(Cat.class);
错误信息:
class com.baeldung.jspec.Dog is not class com.baeldung.jspec.Cat
5.4. 可空性期望
✅ shouldBeNull()
, shouldNotBeNull()
指定对象是否应为 null
:
cage.put(boltDog);
Animal releasedAnimal = cage.release(dogY);
the(releasedAnimal).shouldBeNull();
❌ 失败场景:
the(releasedAnimal).shouldNotBeNull();
错误信息:
Object is null, while it is not expected
5.5. 引用期望
✅ shouldBeTheSameAs()
, shouldNotBeTheSameAs()
指定对象引用是否相同:
Dog firstDog = new Dog("Rex");
Dog secondDog = new Dog("Rex");
$(firstDog).shouldEqual(secondDog);
$(firstDog).shouldNotBeTheSameAs(secondDog);
❌ 失败场景:
$(firstDog).shouldBeTheSameAs(secondDog);
错误信息:
references are not the same, but they should be
5.6. 集合和字符串内容期望
✅ shouldContain()
, shouldNotContain()
指定 Collection
/Map
是否包含元素:
cage.put(tomCat, felixCat);
the(cage.getAnimals()).shouldContain(tomCat);
the(cage.getAnimals()).shouldNotContain(boltDog);
❌ 失败场景:
the(animals).shouldContain(boltDog);
错误信息:
tested value does not contain expected value: Dog [name=Bolt]
也可用于字符串包含检查:
$("Welcome to JSpec demo").shouldContain("JSpec");
⚠️ 特殊用法:通过 toString()
扩展到其他对象:
cage.put(tomCat, felixCat);
the(cage).shouldContain(tomCat); // 检查 cage.toString() 是否包含 tomCat.toString()
6. 自定义期望
6.1. 差异期望
确保代码执行结果与特定值不同:
expect(new DifferenceExpectation<Integer>(4) {
@Override
public Integer exec() {
return 2 + 3;
}
});
实际应用:验证状态变化(如释放动物后笼子数量变化):
cage.put(tomCat, boltDog);
expect(new DifferenceExpectation<Integer>(cage.size()) {
@Override
public Integer exec() {
cage.release(tomCat);
return cage.size();
}
});
❌ 失败场景(释放不存在的动物):
cage.release(felixCat); // 数量未变化
错误信息:
Objects: '2' and '2' are equal, but they should not be
6.2. 异常期望
验证代码是否抛出指定异常:
expect(new ExceptionExpectation<ArithmeticException>(ArithmeticException.class) {
@Override
public void exec() throws ArithmeticException {
System.out.println(1 / 0);
}
});
❌ 失败场景 #1(未抛出异常):
System.out.println(1 / 1);
错误信息:
Expected exception: class java.lang.ArithmeticException, but instead got nothing
❌ 失败场景 #2(抛出异常类型不符):
Integer.parseInt("x");
错误信息:
class java.lang.ArithmeticException,
but instead got: java.lang.NumberFormatException: For input string: "x"
7. 结论
虽然其他流式断言框架在集合断言、异常断言和 Java 8 集成方面更完善,但 JSpec 提供了独特的规范式断言写法。其 API 简洁,支持自然语言风格断言,并提供清晰的测试失败信息。
完整示例代码见 GitHub 仓库(com.baeldung.jspec
包)。