Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
Property-based testing
w języku Scala
2015.04.20
Paweł Grajewski
@BMS_devs
– Edsger W. Dijkstra
"Program testing can be used to show
the presence of bugs, but never
to show their absence!"
Dlaczego tak jest?
Co z tym zrobić?
Rozwiązanie?
• 1991: Spin (Promela)
• 1999: QuickCheck (Haskell)
• Automatyczne generowanie przypadków testowych
• “The pr...
Rozwiązanie?
Czy ktoś to robi?
Telekomunikacja
• Lucent
• seria przełączników PathStar
• potencjalnie największy projekt

tego typu w historii
• Ericsson...
Motoryzacja
• Volvo
• testy oprogramowania mikroprocesorów
• Toyota
• analiza oprogramowania Toyoty Camry w ramach
śledztw...
Misje kosmiczne
• NASA
• Mars Science Laboratory
• Mars Exploration Rover
• Deep Space 1, Cassini, Deep Impact
• algorytm ...
Maeslantkering
• Nieuwe Waterweg k. Rotterdamu
• 200 tys. linii kodu produkcyjnego
• 250 tys. linii kodu testów, symulacji...
A w skali mikro?
QuickCheck
• Pierwsza przystępna i najbardziej popularna implementacja
• Zaimplementowana w języku Haskell:
primes = sieve...
Ale Haskell…?
ScalaCheck
ScalaCheck
• Framework napisany w języku Scala
• Umożliwiający testowanie kodu w językach Scala oraz Java
• Zapewnia:
• Ję...
Język opisu własności
• Operator forAll
• Przykład:
forAll { (text: String) =>
md5(text).matches("^[0-9a-f]{32}$")
}
Generowanie danych
• Wsparcie “z pudełka” dla
generowania wartości typu:
• Boolean
• Byte, Short, Int,
Long, Float, Double...
Generowanie danych
• Przydatne metody do definiowania własnych generatorów:
val colors = Gen.oneOf(“red”, “green”, “blue”)
...
Generowanie danych
• Predefiniowane statyczne generatory:
• Char: Gen.numChar, Gen.alphaLowerChar,
Gen.alphaUpperChar, Gen....
Generowanie danych
• Trait Gen definiuje m.in. map, flatMap, filter, withFilter
• Możliwość wykorzystania w for-comprehensi...
Generowanie danych
• For-comprehension czyni prostym generowanie całych obiektów danych
val nipNoGenerator = Gen.oneOf("84...
Uruchamianie testów
• Najprostszy sposób uruchomienia:
forAll { s: String =>
s.isEmpty
}.check ewentualnie: .check(100000)...
Uruchamianie testów
• Suite’y testowe opisane bezpośrednio z wykorzystaniem ScalaCheck:
object ExampleInScalaCheck extends...
Uruchamianie testów
• Integracja z frameworkami testującymi:
• ScalaTest
• specs2
Przykład w ScalaTest
class ExampleScalaTest extends WordSpec with PropertyChecks {
"String" should {
"be reversible" in {
...
Przykład w specs2 (1/2)
class ExampleSpecs2 extends Specification with ScalaCheck {
"String" should {
"be reversible" in {...
Przykład w specs2 (2/2)
class ExampleSpecs2 extends Specification with ScalaCheck {
def is = s2”""
String should be revers...
Tyle teorii
o ScalaCheck
Kontrowersje
Prominentne projekty
Nietypowe przykłady
• Zbyt infantylne
• s.reverse().reverse() == s
• a+b == c
• Nadmienie teoretyczne (np. algebra, działa...
Lepsze przykłady
• Testowanie logiki biznesowej, która ze swojej natury jest symetryczna:
• serializacja/deserializacja
• ...
Lepsze przykłady
• Testowanie logiki biznesowej, której wynik działania

powinien zachowywać określone właściwości:
forAll...
Lepsze przykłady
• Testowanie interfejsów:
forAll { (company: Company) =>

val output = exportCompany(company)



(output....
Testowanie kodu stanowego
class Counter {
private var n = 0
def inc = n += 1
def dec = if(n > 10) n -= 2 else n -= 1 //bug...
Warto spróbować?
Specyfikacja wymagań
• Testy jednostkowe służące jako specyfikacja
• Odejście od przykładów na rzecz własności
• “(…) an app...
Komu i jak to
pomogło?
Google LevelDB
• Google LevelDB: sortowany key-value store [http://leveldb.org]
• Joe Norton @ Lambda Jam, Chicago 2013
• ...
Google LevelDB
• 1. open new database
• 2. put key1 and val1
• 3. close database
• 4. open database
• 5. delete key2
• 6. ...
pflua
• pflua: filtr pakietów napisany w języku LUA [https://github.com/Igalia/pflua]
• Katerina Barone-Adesi @ FOSDEM, Belgiu...
Języki inne niż Scala?
Java
• junit-quickcheck [https://github.com/pholser/junit-quickcheck]
@RunWith(Theories.class)
public class SymmetricKeyCr...
Java
• Java QuickCheck [https://bitbucket.org/blob79/quickcheck]
@Test public void joinAndSplitTest() {
for (List<String> ...
Groovy/Spock
• z wykorzystaniem generatorów z Java QuickCheck
def 'sum of non-negative numbers should not be negative'() {...
Groovy/Spock
• spock-genesis [https://github.com/Bijnagte/spock-genesis]
def 'test for reversing strings'() {
expect:
def ...
Inne języki
• JavaScript: QC.js, JSVerify
• Clojure: ClojureCheck
• Python: Factcheck, Hypothesis
• Ruby: Rantly
• Mały st...
Przesłanie
Property-based testing
• Kolejne narzędzie, jakie mamy do dyspozycji.
• W niektórych przypadkach faktycznie trudno jest je...
– Edsger W. Dijkstra
“If you want more effective programmers, you
will discover that they should not waste their
time debu...
Pytania?
Dziękuję!
Upcoming SlideShare
Loading in …5
×

4Developers 2015: Property-based testing w języku Scala - Paweł Grajewski

725 views

Published on

Paweł Grajewski

Language: Polish

Techniki Test-driven development (TDD) oraz Behavior-driven development (BDD) są dziś powszechnie stosowaną metodą poprawy jakości wytwarzanego oprogramowania. Obie zakładają w swej konstrukcji budowę zestawu przypadków testowych, ale stworzenie poprawnego i kompletnego zestawu takich przypadków jest nie lada sztuką. Często nie jesteśmy w stanie przewidzieć wszystkich sytuacji brzegowych, w skutek czego nie możemy być pewni na ile w rzeczywistości poprawny jest kod naszej aplikacji

Z pomocą może przyjść nam technika property-based testing, która w miejsce testowania poprawności działania systemu dla skończonego zbioru przypadków testowych wprowadza koncepcję “badania jego właściwości”. Idea ta zrodziła się już wiele lat temu, a dzisiaj znowu wraca do łask wraz ze wzrostem popularności funkcyjnych języków programowania. Zaufało jej już wiele dużych, złożonych projektów m.in. projekt kompilatora języka Scala oraz framework Akka. Prawidłowo zastosowana jest w stanie zapewnić wymierne korzyści - zwiększyć pokrycie kodu testami, ale przede wszystkim uchronić nas przed wieloma typowymi niedopatrzeniami.

Prezentacja przybliży koncepcję property-based testing oraz zademonstruje tę technikę na przykładach.

Published in: Software
  • Be the first to comment

  • Be the first to like this

4Developers 2015: Property-based testing w języku Scala - Paweł Grajewski

  1. 1. Property-based testing w języku Scala 2015.04.20 Paweł Grajewski @BMS_devs
  2. 2. – Edsger W. Dijkstra "Program testing can be used to show the presence of bugs, but never to show their absence!"
  3. 3. Dlaczego tak jest?
  4. 4. Co z tym zrobić?
  5. 5. Rozwiązanie? • 1991: Spin (Promela) • 1999: QuickCheck (Haskell) • Automatyczne generowanie przypadków testowych • “The programmer provides a specification of the program, in the form of properties which functions should satisfy, and QuickCheck then tests that the properties hold in a large number of randomly generated cases.” [source: QuickCheck manual]
  6. 6. Rozwiązanie?
  7. 7. Czy ktoś to robi?
  8. 8. Telekomunikacja • Lucent • seria przełączników PathStar • potencjalnie największy projekt
 tego typu w historii • Ericsson • stacje bazowe dla technologii LTE
  9. 9. Motoryzacja • Volvo • testy oprogramowania mikroprocesorów • Toyota • analiza oprogramowania Toyoty Camry w ramach śledztwa w sprawie tzw. sudden unintended acceleration • analiza prowadzona przez NASA (!)
  10. 10. Misje kosmiczne • NASA • Mars Science Laboratory • Mars Exploration Rover • Deep Space 1, Cassini, Deep Impact • algorytm hand-off pomiędzy CPU • algorytm sterowania silnikami • weryfikacja poprawności działania pamięci flash
  11. 11. Maeslantkering • Nieuwe Waterweg k. Rotterdamu • 200 tys. linii kodu produkcyjnego • 250 tys. linii kodu testów, symulacji oraz oprogramowania pomocniczego
  12. 12. A w skali mikro?
  13. 13. QuickCheck • Pierwsza przystępna i najbardziej popularna implementacja • Zaimplementowana w języku Haskell: primes = sieve [2..] where sieve (p:xs) = p : sieve [x | x <- xs, x `mod` p /= 0] • Później przeniesiona na 25 innych języków programowania
  14. 14. Ale Haskell…?
  15. 15. ScalaCheck
  16. 16. ScalaCheck • Framework napisany w języku Scala • Umożliwiający testowanie kodu w językach Scala oraz Java • Zapewnia: • Język opisu własności, które powinien spełniać kod • Mechanizm generowania danych wejściowych • Mechanizm do uruchamiania testów oraz integrację
 z frameworkami testującymi (specs2 i ScalaTest)
  17. 17. Język opisu własności • Operator forAll • Przykład: forAll { (text: String) => md5(text).matches("^[0-9a-f]{32}$") }
  18. 18. Generowanie danych • Wsparcie “z pudełka” dla generowania wartości typu: • Boolean • Byte, Short, Int, Long, Float, Double • BigInt, BigDecimal • String, Char • Number • Date • Throwable,
 Exception,
 Error • Option[…],
 Either[…, …] • (…, …), (…, …, …), … • Kolekcje np. List[String] • Wielokrotne zagnieżdżenie np. Set[(Set[String], Date)]
  19. 19. Generowanie danych • Przydatne metody do definiowania własnych generatorów: val colors = Gen.oneOf(“red”, “green”, “blue”) val smallInts = Gen.choose(-1000, 1000) val listsOfThreeNumbers = Gen.sequence(List(
 Gen.choose(-10,-1),
 Gen.const(0),
 Gen.choose(1,10)
 )) val vowels = Gen.frequency((3, 'A'), (4, ‘E'), (2, ‘I’),
 (3, 'O'), (1, 'U'), (1, 'Y')) forAll (smallInts) { (n: Int) => … }
  20. 20. Generowanie danych • Predefiniowane statyczne generatory: • Char: Gen.numChar, Gen.alphaLowerChar, Gen.alphaUpperChar, Gen.alphaChar, Gen.alphaNumChar • String: Gen.identifier, Gen.alphaStr, Gen.numStr • Number: Gen.posNum, Gen.negNum • UUID: Gen.uuid forAll (Gen.alphaStr) { (s: String) => … }
  21. 21. Generowanie danych • Trait Gen definiuje m.in. map, flatMap, filter, withFilter • Możliwość wykorzystania w for-comprehension • Łatwość transformacji generatorów w inne generatory val fixedLengthStrings = (n: Int) =>
 Gen.listOfN(n, Gen.alphaChar).map(_.mkString) val evenInts = for (n <- arbitrary[Int]) yield (2 * n) val primeInts = Gen.choose(0, 1000).filter(isPrime(_))
  22. 22. Generowanie danych • For-comprehension czyni prostym generowanie całych obiektów danych val nipNoGenerator = Gen.oneOf("8441900530", "1131946830") val legalFormGenerator = Gen.oneOf(LegalForm.values.toSeq) val companyGenerator = for { name <- arbitrary[String] nipNo <- nipNoGenerator legalForm <- legalFormGenerator } yield Company(name, nipNo, legalForm)
  23. 23. Uruchamianie testów • Najprostszy sposób uruchomienia: forAll { s: String => s.isEmpty }.check ewentualnie: .check(100000) • Wynik działania: ! Falsified after 1 passed tests. > ARG_0: "궯"
  24. 24. Uruchamianie testów • Suite’y testowe opisane bezpośrednio z wykorzystaniem ScalaCheck: object ExampleInScalaCheck extends Properties("String") { property("should be reversible") = forAll { s: String => s.reverse.reverse == s } property("should not be empty when it's length is greater
 than zero") = forAll { s: String => (s.length > 0) ==> !s.isEmpty } }
  25. 25. Uruchamianie testów • Integracja z frameworkami testującymi: • ScalaTest • specs2
  26. 26. Przykład w ScalaTest class ExampleScalaTest extends WordSpec with PropertyChecks { "String" should { "be reversible" in {
 forAll { s: String =>
 assert(s.reverse.reverse == s)
 }
 } "not be empty when it's length is greater than zero" in {
 forAll { s: String =>
 whenever(s.length > 0) {
 assert(!s.isEmpty)
 }
 }
 } }
  27. 27. Przykład w specs2 (1/2) class ExampleSpecs2 extends Specification with ScalaCheck { "String" should { "be reversible" in {
 prop { s: String =>
 s.reverse.reverse == s
 }
 } "not be empty when it's length is greater than zero" in {
 prop { s: String =>
 (s.length > 0) ==> !s.isEmpty
 }
 } }
  28. 28. Przykład w specs2 (2/2) class ExampleSpecs2 extends Specification with ScalaCheck { def is = s2”"" String should be reversible
 ${prop { (s: String) => s.reverse.reverse must_== s }} String should not be empty when it's length is greater than zero
 ${prop { (s: String) => (s.length > 0) ==> !s.isEmpty }} """ }
  29. 29. Tyle teorii o ScalaCheck
  30. 30. Kontrowersje
  31. 31. Prominentne projekty
  32. 32. Nietypowe przykłady • Zbyt infantylne • s.reverse().reverse() == s • a+b == c • Nadmienie teoretyczne (np. algebra, działania na zbiorach, dowody przez indukcję, monoidy itp.) • Rzeczywiście wymagające napisania logiki biznesowej dwa razy (np. walidacja numeru NIP)
  33. 33. Lepsze przykłady • Testowanie logiki biznesowej, która ze swojej natury jest symetryczna: • serializacja/deserializacja • szyfrowanie/odszyfrowywanie • import/eksport • … forAll { (input: String, key: Array[Byte]) =>
 val encrypted: String = encrypt(input, key)
 val decrypted: String = decrypt(encrypted, key)
 
 decrypted == input
 }
  34. 34. Lepsze przykłady • Testowanie logiki biznesowej, której wynik działania
 powinien zachowywać określone właściwości: forAll { (amount: BigDecimal, rate: BigDecimal,
 numberOfMonths: Integer) =>
 
 val schedule = paymentSchedule(amount = amount,
 interestRate = rate,
 numberOfMonths = numberOfMonths)
 
 schedule.map(_.principalPayment).sum == amount
 }
  35. 35. Lepsze przykłady • Testowanie interfejsów: forAll { (company: Company) =>
 val output = exportCompany(company)
 
 (output.vatStatus == true)
 
 (company.legalForm == SP_ZOO) ==>
 (output.representation == true)
 }
  36. 36. Testowanie kodu stanowego class Counter { private var n = 0 def inc = n += 1 def dec = if(n > 10) n -= 2 else n -= 1 //bug! def get = n } scala> CounterSpecification.check ! Falsified after 37 passed tests. > COMMANDS: Inc, Inc, Inc, Inc, Inc, Inc, Inc, Inc, Inc, Inc, Inc, Dec, Get (orig arg: Inc, Dec, Inc, Dec, Inc, Inc, Get, Inc, Inc, Get, Inc, Inc, Inc, Dec, Inc, Inc, Inc, Get, Dec, Inc, Inc, Inc, Dec, Dec, Inc, Get, Dec, Dec, Get, Inc, Dec, Get, Get, Inc, Inc, Inc, Get)
  37. 37. Warto spróbować?
  38. 38. Specyfikacja wymagań • Testy jednostkowe służące jako specyfikacja • Odejście od przykładów na rzecz własności • “(…) an approach to specification using properties instead of tests with "magic" data is an alternative which I think is often shorter and less ambiguous.”
  39. 39. Komu i jak to pomogło?
  40. 40. Google LevelDB • Google LevelDB: sortowany key-value store [http://leveldb.org] • Joe Norton @ Lambda Jam, Chicago 2013 • Opisali model stanów z wykorzystaniem narzędzia QuickCheck • Po kilku minutach od uruchomienia, QuickCheck znalazł sekwencję poleceń prowadzącą do błędu (ciąg 17 zapytań do bazy danych) • Kilka tygodni oczekiwania na poprawkę…
 [https://code.google.com/p/leveldb/issues/detail?id=44] • Po dalszych kilku minutach, QuickCheck znalazł kolejną sekwencję (!), tym razem składającą się z 31 poleceń
  41. 41. Google LevelDB • 1. open new database • 2. put key1 and val1 • 3. close database • 4. open database • 5. delete key2 • 6. delete key1 • 7. close database • 8. open database • 9. delete key2 • 10. close database • 11. open database • 12. put key3 and val1 • 13. close database • 14. open database • 15. close database • 16. open database • 17. seek first • oczekiwana wartość: key1 • otrzymana wartość: key3 (!!!!)
  42. 42. pflua • pflua: filtr pakietów napisany w języku LUA [https://github.com/Igalia/pflua] • Katerina Barone-Adesi @ FOSDEM, Belgium 2015 • Dwie osoby w jedno popołudnie napisały własne narzędzie, które testowało tylko jedną własność: • Input -> IR -> optimize(IR) -> compile -> run() • Input -> IR -> compile -> run() • Po uruchomieniu narzędzie wykryło 7 błędów w już gotowym, działającym, przetestowanym i w miarę dojrzałym projekcie! • Błędy bardzo trudne do wykrycia przy pomocy tradycyjnych technik testowania
  43. 43. Języki inne niż Scala?
  44. 44. Java • junit-quickcheck [https://github.com/pholser/junit-quickcheck] @RunWith(Theories.class) public class SymmetricKeyCryptography { @Theory public void decryptReversesEncrypt( @ForAll String plaintext, @ForAll Key key) { String ciphertext = crypto.encrypt(plaintext, key); assertEquals(plaintext, crypto.decrypt(ciphertext, key)); } }
  45. 45. Java • Java QuickCheck [https://bitbucket.org/blob79/quickcheck] @Test public void joinAndSplitTest() { for (List<String> words : someLists(strings())) { char separator = ','; String joined = Joiner.on(separator).join(words); List<String> output = Splitter.on(separator).split(input); assertEquals(words, output); } }
  46. 46. Groovy/Spock • z wykorzystaniem generatorów z Java QuickCheck def 'sum of non-negative numbers should not be negative'() { expect: list.findAll { it >= 0 }.sum() >= 0 where: list << someLists(integers(), 100) }
  47. 47. Groovy/Spock • spock-genesis [https://github.com/Bijnagte/spock-genesis] def 'test for reversing strings'() { expect: def reversed = string.reverse() reversed.reverse() == string where: string << Gen.these('').then(Gen.string).take(10000) }
  48. 48. Inne języki • JavaScript: QC.js, JSVerify • Clojure: ClojureCheck • Python: Factcheck, Hypothesis • Ruby: Rantly • Mały stopień skomplikowania… napisać samemu?
  49. 49. Przesłanie
  50. 50. Property-based testing • Kolejne narzędzie, jakie mamy do dyspozycji. • W niektórych przypadkach faktycznie trudno jest je zastosować. • W wielu miejscach jego wprowadzenie jest trywialne, a potencjalne zyski bardzo duże. • Możliwe do zaimplementowania w każdym języku programowania. • Niektórzy wprowadzając go do swoich projektów osiągali spektakularne rezultaty • Testy mogą przyjąć postać specyfikacji.
  51. 51. – Edsger W. Dijkstra “If you want more effective programmers, you will discover that they should not waste their time debugging, they should not introduce the bugs to start with.”
  52. 52. Pytania?
  53. 53. Dziękuję!

×