朋友們,我來(lái)了~
今天這一章還是講測(cè)試,基于屬性的測(cè)試(Property-Based Testing)。其實(shí)我感覺(jué)叫隨機(jī)驗(yàn)證測(cè)試更通俗易懂一點(diǎn)。
我們?nèi)粘W约簻y(cè)試的時(shí)候,會(huì)有一個(gè)問(wèn)題,就是我們既是裁判,也是球員。因?yàn)槲覀兊某绦蚓褪琼樦覀兊乃悸穼?xiě)的,測(cè)試也是按照同樣的思路來(lái)測(cè)試的。所以,我們可能通常很難測(cè)出我們自己寫(xiě)出來(lái)的bug。
解決這個(gè)問(wèn)題的一個(gè)辦法,就是把測(cè)試交給其他人。這也是測(cè)試這個(gè)崗位存在的意義之一。但是,如果我們把測(cè)試交給了其他人,上一章提到的,對(duì)測(cè)試的思考可以幫助更好地設(shè)計(jì)程序,這個(gè)好處就不存在了。
基于屬性測(cè)試
解決這個(gè)問(wèn)題的辦法就是,把其他人,換成我們的計(jì)算機(jī),讓計(jì)算機(jī)來(lái)幫我們自動(dòng)測(cè)試。
之前關(guān)于契約或者合約(Contract)那一章中,提到了要給程序制定一個(gè)合同,規(guī)定了輸入的規(guī)則、輸出的規(guī)則、以及不變量(比如給一個(gè)數(shù)組排序,進(jìn)去和出來(lái)的數(shù)組長(zhǎng)度應(yīng)該是不變的)。
那么合約和不變量在這里統(tǒng)稱為屬性(property),很抽象是不是?個(gè)人覺(jué)得它的定義并不重要,舉個(gè)例子就懂了。
比如我們要驗(yàn)證一個(gè)list的排序,我們可以驗(yàn)證這兩件事:1.輸入和輸出的list長(zhǎng)度是否相等;2.是不是list里的每一個(gè)都比它前面一個(gè)大。
用python寫(xiě)出來(lái)就是這樣:
from hypothesis import given
import hypothesis.strategies as some
@given(some.lists(some.Integers()))
def test_list_size_is_invariant_across_sorting(a_list):
original_length = len(a_list)
a_list.sort()
assert len(a_list) == original_length
@given(some.lists(some.text()))
def test_sorted_result_is_ordered(a_list):
a_list.sort()
for i in range(len(a_list) – 1):
assert a_list[i] <= a_list[i + 1]
更重要的是,它利用了hypothesis這個(gè)模塊,@given(some.lists(some.integers()))會(huì)讓它在運(yùn)行的時(shí)候,利用隨機(jī)的數(shù)值把同樣的方法運(yùn)行100次。它會(huì)把出現(xiàn)錯(cuò)誤的情況記錄下來(lái)。
對(duì)數(shù)器
之前在學(xué)習(xí)算法的時(shí)候,也接觸到了一個(gè)叫做對(duì)數(shù)器的概念,其實(shí)和這里的基于屬性測(cè)試,基本上是一件事情。
就是為了驗(yàn)證我們的算法A寫(xiě)對(duì)了,我們先寫(xiě)一個(gè)絕對(duì)正確的算法B,不管效率,只管正確。然后用同樣的數(shù)據(jù)同時(shí)跑算法A和算法B。當(dāng)然也是隨機(jī)跑很多次啦。如果兩個(gè)出現(xiàn)了不同的結(jié)果,那就說(shuō)明算法A寫(xiě)錯(cuò)了。
思路基本相同,只不過(guò),相對(duì)來(lái)說(shuō),可能這個(gè)基于屬性測(cè)試更嚴(yán)謹(jǐn)一點(diǎn),比如,同樣是排序算法,我用我寫(xiě)的冒泡排序算法來(lái)驗(yàn)證我的選擇排序算法,萬(wàn)一,我的冒泡排序也寫(xiě)錯(cuò)了呢?但是,如果我直接從根本上解析出排序(正序)就是后一個(gè)比前一個(gè)大,顯然是更不容易出問(wèn)題的,其實(shí)也能更進(jìn)一步地鍛煉我們尋找根本問(wèn)題的能力。
當(dāng)然啦,嚴(yán)格意義上來(lái)說(shuō),比較后一個(gè)比前一個(gè)大,這個(gè)本身也是一種算法。
實(shí)話說(shuō),要思考清楚有哪些屬性是要測(cè)試的,這件事本身就充滿了難度。如果你平時(shí)沒(méi)有這個(gè)習(xí)慣,突然讓你想,你會(huì)大腦一片空白的,就像是剛學(xué)編程那會(huì),遇到了一個(gè)需求完全不知道從哪里下手的那種感覺(jué)。坦白的說(shuō),我現(xiàn)在就是這種狀態(tài),想必這也是需要刻意練習(xí)的。
這種隨機(jī)大量測(cè)試的方式,可以幫助我們測(cè)出一些邊界值,測(cè)出一些我們想不到的情況。往往最容易出問(wèn)題的地方也是在邊界值的地方。就跟開(kāi)車(chē)似的,車(chē)開(kāi)起來(lái)了,通常都沒(méi)什么問(wèn)題,但是起步可能會(huì)熄火,停車(chē)可能會(huì)倒不進(jìn)去。
Java的相關(guān)框架
關(guān)于這個(gè)基于屬性測(cè)試的框架,我隨手搜了一搜,Python有的,沒(méi)理由咱們Java沒(méi)有,對(duì)不對(duì)?
一、找到兩個(gè)github上開(kāi)源:
1.https://github.com/HypothesisWorks/hypothesis-java
2.https://github.com/quicktheories/QuickTheories
二、找到一個(gè)都已經(jīng)有自己網(wǎng)站的(雖然也有g(shù)ithub):
https://jqwik.net/
三、還有一個(gè)直接是JUnit家的JUnit-Quickcheck(感覺(jué)上這個(gè)更香一點(diǎn))
https://github.com/pholser/junit-quickcheck
我還沒(méi)來(lái)得及仔細(xì)研究,朋友們可以先自行研究起來(lái)~
另一個(gè)實(shí)例
書(shū)中其實(shí)還提到了另外一個(gè)更加實(shí)際一點(diǎn)的例子,但我個(gè)人覺(jué)得那算不上bug,它和實(shí)際的需求有關(guān)系。這邊簡(jiǎn)單提一下吧,或許你也有不同的見(jiàn)解。
大概就是有一個(gè)倉(cāng)庫(kù)類,它可以存放各種貨品,不同貨品有各自的數(shù)量,大概是這樣一個(gè)結(jié)構(gòu)吧:List<Map<String,Integer>>。然后我們可以查詢某個(gè)貨品是否有庫(kù)存、查詢某個(gè)貨品還剩多少庫(kù)存、可以從中取出某個(gè)數(shù)量的貨品。
代碼如下:
class Warehouse:
def __init__(self, stock):
self.stock = stock
def in_stock(self, item_name):
return (item_name in self.stock) and (self.stock[item_name] > 0)
def take_from_stock(self, item_name, quantity):
if quantity <= self.stock[item_name]:
self.stock[item_name] -= quantity
else:
raise Exception("Oversold {}".format(item_name))
def stock_count(self, item_name):
return self.stock[item_name]
倉(cāng)庫(kù)的初始化是這樣的:
wh = Warehouse({"shoes": 10, "hats": 2, "umbrellas": 0})
然后,同樣用了hypothesis來(lái)做批量測(cè)試,然后在調(diào)用take_from_stock( item_name='hats', quantity=3)這樣一組數(shù)據(jù)的時(shí)候報(bào)錯(cuò)了。
作者說(shuō),在in_stock我們不應(yīng)該只判斷庫(kù)存是不是大于0,而是要判斷它有沒(méi)有包含我們要拿取的貨品數(shù)。代碼應(yīng)該改成這樣:
def in_stock(self, item_name, quantity):
return (item_name in self.stock) and (self.stock[item_name] >= quantity)
反正我是覺(jué)得這不算個(gè)bug吧,畢竟在真正獲取貨品的時(shí)候,就報(bào)錯(cuò)了呀。要看我們對(duì)于in_stock這個(gè)方法本身的要怎么定義咯,是只需要知道它還有沒(méi)有庫(kù)存,還是需要知道它有沒(méi)有我需要的庫(kù)存。
雖然實(shí)際需求中,后者可能性更大,但是在take_from_stock方法里報(bào)錯(cuò),又有什么問(wèn)題呢?(又或者,作者只是想舉個(gè)例子,是我太較真了)
尾聲
基于屬性的測(cè)試是對(duì)于單元測(cè)試的補(bǔ)充,對(duì)于單元測(cè)試的思考,可以讓我們思考代碼實(shí)現(xiàn)的其他方式?;趯傩缘臏y(cè)試,可以讓我們更加清晰,我們的方法能干什么不能干什么,同時(shí),也消除一些意外的情況。
如果,你還沒(méi)有把這兩種測(cè)試用起來(lái),現(xiàn)在就趕緊用起來(lái)吧~
如若轉(zhuǎn)載,請(qǐng)注明出處:http://www.51zclw.cn/archives/25944