Property-based testing
Magda Stożek
Let your testing library work for you
Property-based testing: テストライブラリ活用方法
def calculateDiscount(customer: Customer, now: LocalDate): BigDecimal
Example: Calculating discount
Years:
0-1
1-2
2 +
0%
30%
10%
20%
birthday
case class Customer(
name: String,
joinedAt: LocalDate,
dateOfBirth: LocalDate
)
顧客の継続年数によって割引率を計算する
Example-based tests
it "should calculate no didcount" in {...}
it should "calculate 10 percent discount" in {...}
it should "calculate 20 percent discount" in {...}
it should "calculate birthday discount" in {...}
it "should calculate 20 percent discount for 3 years" in {...}
Happy path
Edge cases
例ベースのテストならこんな感じ
What about properties?
プロパティベーステストの世界では、これがどのように異なるか見てみましょう
def calculateDiscount(customer: Customer, now: LocalDate): BigDecimal
Example: Calculating discount
Years:
0-1
1-2
2 +
0%
30%
10%
20%
birthday
case class Customer(
name: String,
joinedAt: LocalDate,
dateOfBirth: LocalDate
)
どんなルールがあるか考えてみましょう
Property-based tests
it "should not be lower than 0" in {...}
it "should not be over 30 percent" in {...}
it "should be proportional to membership years" in {...}
Properties:
it "should not be greater than birthday discount" in {...}
四つのルール
property("Discount should not be over 30 percent") {
forAll { (customer: Customer, now: LocalDate) =>
val discount = calculator.calculateDiscount(customer, now)
discount should be <= BigDecimal(0.3)
}
}
Property: discount is never larger than 30%
プロパティベーステストの書き方
ScalaTestFailureLocation: pbt.DiscountCalculatorPropertySpec at (DiscountCalculatorPropertySpec.scala:28)
org.scalatest.exceptions.GeneratorDrivenPropertyCheckFailedException: DateTimeException was thrown during property evaluation.
Message: Invalid date 'February 29' as '2049' is not a leap year
Occurred when passed generated values (
arg0 = Customer(nWtCZLoAQ,2009-04-14,1988-02-29),
arg1 = Customer(bLNkLdYmCYCjVJlIDRWumkWZceQHyoulrCcietBLPyfBCqbyXqub,2015-12-19,1996-12-13),
arg2 = 2049-04-09
)
Discovered error
見つかったエラーの例
Generators
データを生成する
Custom generators
val customerGen: Gen[Customer] = for {
name <- Gen.alphaStr
age <- Gen.choose(12, 120)
subscription <- Gen.option(Gen.oneOf("Daily", "Monthly", "Yearly")
dateJoined <- joinedGen()
} yield Customer(name, age, subscription, dateJoined)
カスタムクラスのジェネレーター
Challenges
プロパティベーステストの課題
Difficult questions earlier
Challenge #1
難題が前倒しになります
Nondeterminism
Challenge #2
ランダム性があるため、バグが後から見つかる可能性があります
Coming up with properties
Challenge #3
どんなルールがあるか考えてみましょう
Strategies
1. Examples first, properties later
2. Properties for key components
(1)例ベーステストから書きます、それからプロパティベーステスト (2)重要なコンポーネントだけプロパティベーステストを書きます
Summary
- Higher cost, higher value
- Edge cases and misconceptions
- Coming up with properties
- Balance
(1)高いコストだが高品質 (2)エッジケースと誤解を見つける (3)プロパティを思いつけるようにする (4)二種類のテストのバランスを取る
More
参考資料
Questions?
property("End of presentation") {
forAll { (attendee: Attendee) =>
whenever(attendee.hasQuestions) {
attendee.questionsAsked should be > 0
}
}
}
ご清聴ありがとうございました
Property-based testing [Scala, 20 min]
By Magda Stożek
Property-based testing [Scala, 20 min]
- 372