概述
軟件測試是軟件開發中必不可少的流程之一,但是軟件測試又全部都是測試人員的工作,作爲開發人員最好也承擔其中的一部分工作,因爲開發人員瞭解自己的功能需要覆蓋哪些必要的場景,而測試人員是幫你找到你沒有覆蓋到的場景。而且寫單測用例能夠有效的幫助項目做CI與DI。所以,既然是一件不可避免的事,我們何不讓其變得簡單呢。
依賴與基礎
本人的項目環境如下:
JDK8,Spring Boot 2.2.0.RELEASE
要使用Groovy+Spock編寫單測,首先引入如下Maven依賴,同時安裝Groovy插件。
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-core</artifactId>
<version>1.2-groovy-2.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.spockframework</groupId>
<artifactId>spock-spring</artifactId>
<version>1.2-groovy-2.4</version>
<scope>test</scope>
</dependency>
接下來我們來講一下Groovy的常見語法,作爲一門動態語言,它的寫法比Java簡單很多,而且基本只要你會寫Java,你很容易就可以上手Groovy。
首先看一下測試模板方法的定義和JUnit的對比:
Spock的模板方法說明:
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
常見用法
expect-where
有時,裏面的某個測試用例失敗了,卻難以查到是哪個失敗了。這時候,可以使用Unroll註解,該註解會將where子句的每個測試用例轉化爲一個 @Test 獨立測試方法來執行,這樣就很容易找到錯誤的用例。 方法名還可以更可讀些。
@SpringBootTest(classes = BootstrapApplication)
@Transactional
@Rollback
class StaffQueryServiceSpec extends Specification {
@Autowired
private StaffQueryService staffQueryService;
@Unroll
def "通過歸屬人ID查詢員工"(){
expect:
staffQueryService.getStaffById(ownerId)!=null
where:
ownerId | _
1 | _
2 | _
}
}
where後面的條件可以通過表格的形式或者數據管道的形式,單個數據的時候可以通過“_”構建表格
def "通過歸屬人ID查詢員工"(){
expect:
staffQueryService.getStaffById(ownerId)!=null
where:
ownerId << [1,2,3]
}
given-expect-where
@SpringBootTest(classes = BootstrapApplication)
@Transactional
@Rollback
class OrderQueryServiceSpec extends Specification {
@Autowired
private OrderQueryService orderQueryService;
@Unroll
def "根據經銷商機構判斷是否存在在途訂單"() {
given:
def orgId = 1
def dealerId = dealer
expect:
orderQueryService.hasOnWayOrder(dealerId, orgId) == result
where:
dealer | result
1 | true
9999 | false
}
}
given-when-then
這個比較符合敏捷開發中用戶故事的描述,也是用的最多的一種寫法。
@SpringBootTest(classes = BootstrapApplication)
@Transactional
@Rollback
class OrderAppServiceSpec extends Specification {
@Autowired
private OrderAppService orderAppService;
def "取消訂單"() {
given:
def dto = new CancelOrderDTO(operatorId: "1", operatorName: "tzx", dealerId: 1)
when:
dto.setOrderNo("OR202001020014")
orderAppService.cancelOrder(dto)
then:
noExceptionThrown()
when:
dto.setOrderNo("OR202001020010")
orderAppService.cancelOrder(dto)
then:
thrown(Exception)
}
}
given-when-then-where
@SpringBootTest(classes = BootstrapApplication)
@Transactional
@Rollback
class OrderAppServiceSpec extends Specification {
@Autowired
private OrderAppService orderAppService;
def "取消訂單"() {
given:
def dto = new CancelOrderDTO(operatorId: operatorId, operatorName: operatorName, dealerId: dealerId)
when:
dto.setOrderNo("OR202001020014")
orderAppService.cancelOrder(dto)
then:
noExceptionThrown()
where:
operatorId| operatorName | dealerId
1| "tzs" | 1
2| "zhangsan" | 1
3 | "wangwu" | 2
}
}
如果有多個條件需要測試時可以採用這種寫法
given-when-then-thrown
在 when 子句中調用了會拋出異常的方法,而在 then 子句中,使用 thrown 接收方法拋出的異常,並賦給指定的變量 ex, 之後就可以對 ex 進行斷言了。
@SpringBootTest(classes = BootstrapApplication)
@Transactional
@Rollback
class CustomerAppServiceSpec extends Specification {
@Autowired
private CustomerAppService customerAppService;
def "更新未存在客戶" (){
given:
def dto = new CustomerCreateDTO(customerId: -1,customerName: "小王");
when: "客戶不存在時"
customerAppService.updateCustomer(dto);
then:
def ex = thrown(ConnectorBusinessException)
ex.class.name = "java.lang.RuntimeException"
ex.cause.class.name = "com.souche.connector.common.exception.ConnectorBusinessException"
}
}
打樁
很多時候我們測試的時候不需要執行真正的方法,因爲在測試代碼裏面構建一個真實的對象是比較麻煩的,特別是使用一些依賴注入框架的時候,因此有了打樁的功能。
比如我們依賴某個外部應用的service的返回值,可以採用如下的方式。
@SpringBean
UserCenterQueryService userCenterQueryService = Mock{
getUserByShopCodeAndRole(_,_) >> "001"
};
如果需要定義多次調用返回不同的返回值可以採用下面的方式
@SpringBean
UserCenterQueryService userCenterQueryService = Mock{
getUserByShopCodeAndRole(_,_) >>> ["001","002"]
};
總結
本文介紹了expect-where,given-expect-where,given-when-then,given-when-then-where
given-when-then-thrown,打樁等用法,相信已經適用了絕大多數場景。