In a previous prost, I explained property based testing. In this post we’ll see a simple example using golang’s built-in quick package.
Let assume we’re building the same bank transfer code as described in the dotnet FsCheck earlier post.
Here’s the golang equivalent of the test:
package goquickcheckexample
import (
"testing"
"testing/quick"
)
func TestProperties(t *testing.T) {
bank := BuggyBank{}
properties := func(StartBalanceA int, StartBalanceB int, TransferAmount int) bool {
bank.BalanceOfAccountA = StartBalanceA
bank.BalanceOfAccountB = StartBalanceB
err := bank.Transfer(TransferAmount)
if err != nil {
//Transfer failed
balancesChanged := (bank.BalanceOfAccountA != StartBalanceA) || (bank.BalanceOfAccountB != StartBalanceB)
if balancesChanged {
t.Log("Balances changed on failed transfer")
}
return !balancesChanged
}
//Transfer succeeded
balanceAIsNonNegative := bank.BalanceOfAccountA >= 0
balanceAChanged := bank.BalanceOfAccountA != StartBalanceA
balanceBchanged := bank.BalanceOfAccountB != StartBalanceB
if !balanceAIsNonNegative {
t.Log("Balance of A ended negative")
}
if !balanceAChanged {
t.Log("Balance of A did not change")
}
if !balanceBchanged {
t.Log("Balance of B did not change")
}
return balanceAIsNonNegative && balanceAChanged && balanceBchanged
}
c := quick.Config{MaxCount: 1000000}
if err := quick.Check(properties, &c); err != nil {
t.Error(err)
}
}
Note: If you want all test runs to use the same set of random numbers then use: c := quick.Config{MaxCount: 1000000, Rand: rand.New(rand.NewSource(0))}
When I ran the test, it detected a defect: Transfers succeed even when the source account’s balance is insufficient:
bank_test.go:28: Balance of A ended negative bank_test.go:41: #2: failed on input 6319534437456565100, -3125004238116898490, 8226184717426742479
After fixing that bug, it detected a defect: The code allowed transferring negative amounts:
bank_test.go:34: Balance of A ended negative bank_test.go:47: #22: failed on input 5995030153294015290, -7891513722668943486, -3464545538278061921
While analyzing this defect we notice yet another one: This code is not safe against integer overflow.