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

53
Property-based testing w języku Scala 2015.04.20 Paweł Grajewski @BMS_devs

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

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

Property-based testing w języku Scala

2015.04.20

Paweł Grajewski

@BMS_devs

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

– Edsger W. Dijkstra

"Program testing can be used to show the presence of bugs, but never

to show their absence!"

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

Dlaczego tak jest?

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

Co z tym zrobić?

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

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]

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

Rozwiązanie?

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

Czy ktoś to robi?

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

Telekomunikacja• Lucent

• seria przełączników PathStar

• potencjalnie największy projekttego typu w historii

• Ericsson

• stacje bazowe dla technologii LTE

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

Motoryzacja• Volvo

• testy oprogramowania mikroprocesorów

• Toyota

• analiza oprogramowania Toyoty Camry w ramach śledztwa w sprawie tzw. sudden unintended acceleration

• analiza prowadzona przez NASA (!)

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

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

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

Maeslantkering• Nieuwe Waterweg k. Rotterdamu

• 200 tys. linii kodu produkcyjnego

• 250 tys. linii kodu testów, symulacji oraz oprogramowania pomocniczego

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

A w skali mikro?

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

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

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

Ale Haskell…?

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

ScalaCheck

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

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)

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

Język opisu własności• Operator forAll

• Przykład:

forAll { (text: String) =>

md5(text).matches("^[0-9a-f]{32}$")

}

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

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)]

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

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) => … }

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

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) => … }

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

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(_))

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

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)

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

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: "궯"

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

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

}

}

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

Uruchamianie testów• Integracja z frameworkami testującymi:

• ScalaTest

• specs2

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

Przykład w ScalaTestclass 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) } } }

}

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

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 } }

}

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

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 }}

"""

}

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

Tyle teorii o ScalaCheck

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

Kontrowersje

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

Prominentne projekty

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

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)

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

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}

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

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}

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

Lepsze przykłady• Testowanie interfejsów:

forAll { (company: Company) => val output = exportCompany(company) (output.vatStatus == true) (company.legalForm == SP_ZOO) ==> (output.representation == true) }

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

Testowanie kodu stanowegoclass 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)

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

Warto spróbować?

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

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.”

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

Komu i jak to pomogło?

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

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ń

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

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 (!!!!)

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

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

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

Języki inne niż Scala?

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

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));

}

}

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

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);

}

}

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

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)

}

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

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)

}

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

Inne języki• JavaScript: QC.js, JSVerify

• Clojure: ClojureCheck

• Python: Factcheck, Hypothesis

• Ruby: Rantly

• Mały stopień skomplikowania… napisać samemu?

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

Przesłanie

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

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.

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

– 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.”

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

Pytania?

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

Dziękuję!