用 Spock 单元测试框架替代 JUnit

本贴最后更新于 2220 天前,其中的信息可能已经天翻地覆

Spock 是一个 Java 及 Groovy 应用的测试框架。它之所以能从众多框架中脱颖而出,是由于它富有表现力的语言。通过 JUnit runner,Spock 能够与大多数 IDE、构建工具及集成测试服务兼容。Spock 的诞生受到了 JUnit, jMock, RSpec, Groovy, Scala, Vulcans 的启发。

Groovy VS Java

此处不做细节上的比较,只提在写单元测试中用到的代码

  1. 类型推断

    orderData.setOrderId(123456L)
    orderMoney.setInitFactPrice(new BigDecimal("3.2"));
    
    orderData.orderId = 123456
    orderMoney.initFactprice = 3.2
    
  2. 输出结果

    System.out.println("Hello world");
    
    println("Hello world")
    
  3. 创建 List

    List<String> list = new ArrayList<>();
    list.add("test1");
    list.add("test2");
    // 或使用guava
    List<String> list = Lists.newArrayList("test1","test2");
    
    OrderData odtest1 = new OrderData();
    odtest1.setOrderId(123456L);
    odtest1.setStoreId(123L);
    OrderData odtest2 = new OrderData();
    ...
    List<OrderData> orderDataList = Lists.newArrayList();
    orderDataList.add(odtest1);
    orderDataList.add(odtest2);
    
    
    def list = ["test1","test2"]
    def orderDataList = [new OrderData(orderId:123456,storeId:123),
                         new OrderData(orderId:123456,storeId:234)
                        ]
    // 或
    orderDataList << new OrderData(orderId:123456,storeId:123)
    orderDataList << new OrderData(orderId:123456,storeId:124)
    
  4. 对象比较

    odtest1.getOrderId() == odtest2.getOrderId();
    odtest1.getInitFactPrice().compareTo(odtest2.getInitFactPrice()) == 0;
    //不适用java 8 提供的Stream方法下,判断数组的步骤要更多,如size比较,对应对象或值比较,此处意会即好
    for(int i=0;i<list1.size;i++){
        list1.get(i).equals(list2.get(i))
    }
    
    odtest1 == odtest2
    test1 == test2
    

更简洁的书写语法,能够让你在对测试有限的热情里写出更多的测试用例

Get Started With Spock

import spock.lang.*
class MyFirstSpecification extends Specification {
    // fields
    def coll = new Collaborator()
    @Shared res = new VeryExpensiveResource()
    // fixture methods
    def setup() {}          // run before every feature method
    def cleanup() {}        // run after every feature method
    def setupSpec() {}     // run before the first feature method
    def cleanupSpec() {}   // run after the last feature method
    // feature methods
    def "pushing an element on the stack"() {
      // blocks go here
    }
    // helper methods
    def testHelper(){
        
    }
}

blocks

setup -> 测试准备

clean -> 测试收尾

where -> 循环遍历

when/then : 给定……则满足……

expect:应满足……

Spock VS JUnit

简单的单元测试,测试代码的差异主要在 Java 和 Groovy 的语法差异上,而参数化的单元测试,能够明显的感受到两个测试框架的不同,故此处以参数化测试为例:

// 24 lines
@RunWith(Parameterized.class)
public class MyTest {
    private String name;
    private String family;


    public MyTest(String name,String family) {
        super();
        this.name = name;
        this.family = family;
    }

    @Parameterized.Parameters
    public static Collection input() {
        return Arrays.asList(new Object[][]{
                {"Zephyr","Jung"}, {"Y","Z"}
        });
    }

    @Test
    public void test(){
        System.out.println(name+" "+family);
    }
}
// 10 lines
@Unroll("#name #family")
class MyTest2 extends Specification {
    def "test"() {
        expect:
        println(name + " " + family)
        where:
        name     | family
        "zephyr" | "Jung"
        "Y"     | "Z"
    }
}

这个单元测试中的参数是简单的字符串,我们可以看到,使用 jUnit 来实现参数化的单元测试,写法要比 Spock 更为复杂,当参数是对象时,又会附加上 Java 代码的复杂性,大量的时间用在了模板性质的代码上。

通过 @Unroll 注解,能够将每一个参数作为标题展现在测试结果中

在 SpringBoot 项目中添加 Spock 测试

<dependency>
    <groupId>org.spockframework</groupId>
    <artifactId>spock-spring</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
@SpringBootTest
@ContextConfiguration(classes = Application.class)
class MyTest extends Specification {
    @Autowired
    private TicketCenter ticketCenter
    @Autowired
    private Train train

    def "testSpring"() {
        when:
        Ticket ticket = ticketCenter.getTicket(new Path(start, end, date))
        train.setTicket(ticket)
        train.travel()
        then:
        true
        where:
        start       | end         | date
        "zhengzhou" | "shanghai"  | new Date()
        "shanghai"  | "zhengzhou" | new Date()

    }
}

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...