比特幣核心中添加了基於屬性的測試。它們是什麼,它們可以替代比特幣核心中的一些單元測試嗎?
比特幣核心中添加了基於屬性的測試。
這篇開發文章將基於屬性的測試描述為隨機單元測試,在某些情況下可以替代單元測試。
“一般來說,基於屬性的測試只需要幾行程式碼(如單元測試),但與單元測試不同,它們每次測試一組不同的輸入。因此,您最終會以大致相同的方式覆蓋更多的域空間測試程式碼的數量。”
這是一個很好的總結嗎?我們可以逐步淘汰比特幣核心中的一些單元測試,並用基於屬性的測試來代替它們嗎?
我認為這是一個公平的總結。我會猶豫逐步淘汰單元測試,而是用基於屬性的測試來補充它們。在我維護的一個名為bitcoin-s的庫中,我們將它們串聯使用。
概念
基於屬性的測試系統有兩個主要概念。
第一個是一個
Generator
。生成器的目的是生成要在測試中執行的值。將生成器視為指定要為測試生成的隨機值的邊界。第二個概念是測試本身。在英語中,基於屬性的測試說
“給定一個生成器 X,我想從 X 生成隨機值並通過我的測試案例執行它們。我的斷言應該始終適用於從我的生成器生成的值”。
一個例子
我將從這個我已經打開很長時間的 PR 中提取一個範例。如果您想查看一些更廣泛的測試,我建議您查看一下。
我們要斷言的“屬性”是
“如果我們在比特幣中生成兩個隨機私鑰,它們不應該是相同的”
如果這個屬性失敗,我們有一個跡象表明我們的偽隨機數生成器可能有問題。
這是帶有比特幣核心的 C++ 中的測試案例的樣子:
/** Check CKey uniqueness */ RC_BOOST_PROP(key_uniqueness, (const CKey& key1, const CKey& key2)) { RC_ASSERT(!(key1 == key2)); }
該測試案例可以執行
N
多次(在rapidcheck library中預設為 10 次。下一個合乎邏輯的問題將是“從哪裡做
key1
和key2
從哪裡來”?答案:它們來自發電機!基於屬性的測試的美妙之處在於利用框架為您生成值。下面是生成器的
CKey
樣子:/** Generator for a new CKey */ template <> struct Arbitrary<CKey> { static Gen<CKey> arbitrary() { return rc::gen::map<int>([](int x) { CKey key; key.MakeNewKey(true); return key; }); }; };
這使rapidcheck能夠生成任意有效
CKey
的 s 以作為參數傳遞給我們斷言為真的屬性。如果您想查看非平凡的生成器/斷言,您也可以在該 PR 中查看與腳本相關的更複雜的生成器和斷言。
概括
每次使用新生成的值多次執行測試案例非常有用。通常在軟體測試中,您編寫一個單元測試來斷言您的程式碼對於一個或兩個值按預期工作。這是一種非常有限的測試方法,因為您希望您的程式碼適用於一系列值。使用生成器,您可以讓電腦生成一系列有效值,您的屬性應在這些值上斷言為真。
我認為這對於像比特幣這樣的共識系統特別有用,在這種系統中,我們需要推理作為共識系統輸入的各種值。在推理極端情況時,基於屬性的測試是一個很好的工具,可以用來減輕一些認知負擔。