SlideShare une entreprise Scribd logo
1  sur  53
Télécharger pour lire hors ligne
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 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]
Rozwiązanie?
Czy ktoś to robi?
Telekomunikacja
• Lucent
• seria przełączników PathStar
• potencjalnie największy projekt

tego typu w historii
• Ericsson
• stacje bazowe dla technologii LTE
Motoryzacja
• Volvo
• testy oprogramowania mikroprocesorów
• Toyota
• analiza oprogramowania Toyoty Camry w ramach
śledztwa w sprawie tzw. sudden unintended
acceleration
• analiza prowadzona przez NASA (!)
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
Maeslantkering
• Nieuwe Waterweg k. Rotterdamu
• 200 tys. linii kodu produkcyjnego
• 250 tys. linii kodu testów, symulacji oraz oprogramowania
pomocniczego
A w skali mikro?
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
Ale Haskell…?
ScalaCheck
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)
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
• BigInt, BigDecimal
• String, Char
• Number
• Date
• Throwable,

Exception,

Error
• Option[…],

Either[…, …]
• (…, …), (…, …, …), …
• Kolekcje np. List[String]
• Wielokrotne zagnieżdżenie np.
Set[(Set[String], Date)]
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) => … }
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) => … }
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(_))
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)
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: "궯"
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
}
}
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 {

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)

}

}

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

}

}
}
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 }}
"""
}
Tyle teorii
o ScalaCheck
Kontrowersje
Prominentne projekty
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)
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

}
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

}
Lepsze przykłady
• Testowanie interfejsów:
forAll { (company: Company) =>

val output = exportCompany(company)



(output.vatStatus == true)



(company.legalForm == SP_ZOO) ==>

(output.representation == true)

}
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)
Warto spróbować?
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.”
Komu i jak to
pomogło?
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ń
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 (!!!!)
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
Języki inne niż Scala?
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));
}
}
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);
}
}
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)
}
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)
}
Inne języki
• JavaScript: QC.js, JSVerify
• Clojure: ClojureCheck
• Python: Factcheck, Hypothesis
• Ruby: Rantly
• Mały stopień skomplikowania… napisać samemu?
Przesłanie
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.
– 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.”
Pytania?
Dziękuję!

Contenu connexe

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

Michał Dec - Quality in Clouds
Michał Dec - Quality in CloudsMichał Dec - Quality in Clouds
Michał Dec - Quality in Cloudskraqa
 
Confitura 2018 - Sekretne życie jobów Sparkowych
Confitura 2018 - Sekretne życie jobów SparkowychConfitura 2018 - Sekretne życie jobów Sparkowych
Confitura 2018 - Sekretne życie jobów SparkowychMarcin Jasiński
 
ETW w służbie programisty .NET
ETW w służbie programisty .NETETW w służbie programisty .NET
ETW w służbie programisty .NETKonrad Kokosa
 
Wprowadzenie do języka Swift, czyli nowe podejście do programowania aplikacji...
Wprowadzenie do języka Swift, czyli nowe podejście do programowania aplikacji...Wprowadzenie do języka Swift, czyli nowe podejście do programowania aplikacji...
Wprowadzenie do języka Swift, czyli nowe podejście do programowania aplikacji...The Software House
 
KraQA VIII - Techniki projektowania testów
KraQA VIII - Techniki projektowania testów KraQA VIII - Techniki projektowania testów
KraQA VIII - Techniki projektowania testów kraqa
 
Metaprogramowanie w JS
Metaprogramowanie w JSMetaprogramowanie w JS
Metaprogramowanie w JSDawid Rusnak
 
[PL] Jak programować aby nie zwariować
[PL] Jak programować aby nie zwariować[PL] Jak programować aby nie zwariować
[PL] Jak programować aby nie zwariowaćJakub Marchwicki
 
Programowanie Równolegle - Parallel Extensions
Programowanie Równolegle - Parallel ExtensionsProgramowanie Równolegle - Parallel Extensions
Programowanie Równolegle - Parallel ExtensionsWojciech Grześkowiak
 
AADays 2015 - Jak to zrobic w JavaScript
AADays 2015 - Jak to zrobic w JavaScriptAADays 2015 - Jak to zrobic w JavaScript
AADays 2015 - Jak to zrobic w JavaScriptJacek Okrojek
 
SCAP – standaryzacja formatów wymiany danych w zakresie bezpieczeństwa IT
SCAP – standaryzacja formatów wymiany danych w zakresie bezpieczeństwa ITSCAP – standaryzacja formatów wymiany danych w zakresie bezpieczeństwa IT
SCAP – standaryzacja formatów wymiany danych w zakresie bezpieczeństwa ITRedge Technologies
 
[Quality Meetup #9] TestOps, QAOps - czy ktoś taki istnieje? - Aleksandra Kor...
[Quality Meetup #9] TestOps, QAOps - czy ktoś taki istnieje? - Aleksandra Kor...[Quality Meetup #9] TestOps, QAOps - czy ktoś taki istnieje? - Aleksandra Kor...
[Quality Meetup #9] TestOps, QAOps - czy ktoś taki istnieje? - Aleksandra Kor...Future Processing
 
TDD drogą do oświecenia w Scali
TDD drogą do oświecenia w ScaliTDD drogą do oświecenia w Scali
TDD drogą do oświecenia w ScaliKonrad Malawski
 
Integracja środowiska testowego z użyciem Robot Framework, TrojQA 2014-12-16
Integracja środowiska testowego z użyciem Robot Framework, TrojQA 2014-12-16Integracja środowiska testowego z użyciem Robot Framework, TrojQA 2014-12-16
Integracja środowiska testowego z użyciem Robot Framework, TrojQA 2014-12-16Krzysztof Synak
 
ETW w służbie programisty .NET
ETW w służbie programisty .NETETW w służbie programisty .NET
ETW w służbie programisty .NETKonrad Kokosa
 

Similaire à 4Developers 2015: Property-based testing w języku Scala - Paweł Grajewski (20)

Scala
ScalaScala
Scala
 
Michał Dec - Quality in Clouds
Michał Dec - Quality in CloudsMichał Dec - Quality in Clouds
Michał Dec - Quality in Clouds
 
Confitura 2018 - Sekretne życie jobów Sparkowych
Confitura 2018 - Sekretne życie jobów SparkowychConfitura 2018 - Sekretne życie jobów Sparkowych
Confitura 2018 - Sekretne życie jobów Sparkowych
 
ETW w służbie programisty .NET
ETW w służbie programisty .NETETW w służbie programisty .NET
ETW w służbie programisty .NET
 
Wprowadzenie do języka Swift, czyli nowe podejście do programowania aplikacji...
Wprowadzenie do języka Swift, czyli nowe podejście do programowania aplikacji...Wprowadzenie do języka Swift, czyli nowe podejście do programowania aplikacji...
Wprowadzenie do języka Swift, czyli nowe podejście do programowania aplikacji...
 
Benchmarking
Benchmarking Benchmarking
Benchmarking
 
KraQA VIII - Techniki projektowania testów
KraQA VIII - Techniki projektowania testów KraQA VIII - Techniki projektowania testów
KraQA VIII - Techniki projektowania testów
 
Metaprogramowanie w JS
Metaprogramowanie w JSMetaprogramowanie w JS
Metaprogramowanie w JS
 
[PL] Jak programować aby nie zwariować
[PL] Jak programować aby nie zwariować[PL] Jak programować aby nie zwariować
[PL] Jak programować aby nie zwariować
 
Programowanie Równolegle - Parallel Extensions
Programowanie Równolegle - Parallel ExtensionsProgramowanie Równolegle - Parallel Extensions
Programowanie Równolegle - Parallel Extensions
 
DSL - DYI
DSL - DYIDSL - DYI
DSL - DYI
 
AADays 2015 - Jak to zrobic w JavaScript
AADays 2015 - Jak to zrobic w JavaScriptAADays 2015 - Jak to zrobic w JavaScript
AADays 2015 - Jak to zrobic w JavaScript
 
SCAP – standaryzacja formatów wymiany danych w zakresie bezpieczeństwa IT
SCAP – standaryzacja formatów wymiany danych w zakresie bezpieczeństwa ITSCAP – standaryzacja formatów wymiany danych w zakresie bezpieczeństwa IT
SCAP – standaryzacja formatów wymiany danych w zakresie bezpieczeństwa IT
 
[Quality Meetup #9] TestOps, QAOps - czy ktoś taki istnieje? - Aleksandra Kor...
[Quality Meetup #9] TestOps, QAOps - czy ktoś taki istnieje? - Aleksandra Kor...[Quality Meetup #9] TestOps, QAOps - czy ktoś taki istnieje? - Aleksandra Kor...
[Quality Meetup #9] TestOps, QAOps - czy ktoś taki istnieje? - Aleksandra Kor...
 
TDD drogą do oświecenia w Scali
TDD drogą do oświecenia w ScaliTDD drogą do oświecenia w Scali
TDD drogą do oświecenia w Scali
 
Monitoring sieci
Monitoring sieciMonitoring sieci
Monitoring sieci
 
Integracja środowiska testowego z użyciem Robot Framework, TrojQA 2014-12-16
Integracja środowiska testowego z użyciem Robot Framework, TrojQA 2014-12-16Integracja środowiska testowego z użyciem Robot Framework, TrojQA 2014-12-16
Integracja środowiska testowego z użyciem Robot Framework, TrojQA 2014-12-16
 
Mongodb with Rails
Mongodb with RailsMongodb with Rails
Mongodb with Rails
 
ETW w służbie programisty .NET
ETW w służbie programisty .NETETW w służbie programisty .NET
ETW w służbie programisty .NET
 
Android i REST
Android i RESTAndroid i REST
Android i REST
 

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

  • 1. Property-based testing w języku Scala 2015.04.20 Paweł Grajewski @BMS_devs
  • 2. – Edsger W. Dijkstra "Program testing can be used to show the presence of bugs, but never to show their absence!"
  • 4. Co z tym zrobić?
  • 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]
  • 7. Czy ktoś to robi?
  • 8. Telekomunikacja • Lucent • seria przełączników PathStar • potencjalnie największy projekt
 tego typu w historii • Ericsson • stacje bazowe dla technologii LTE
  • 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. 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. Maeslantkering • Nieuwe Waterweg k. Rotterdamu • 200 tys. linii kodu produkcyjnego • 250 tys. linii kodu testów, symulacji oraz oprogramowania pomocniczego
  • 12. A w skali mikro?
  • 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
  • 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. Język opisu własności • Operator forAll • Przykład: forAll { (text: String) => md5(text).matches("^[0-9a-f]{32}$") }
  • 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. 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. 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. 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. 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. 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. 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. Uruchamianie testów • Integracja z frameworkami testującymi: • ScalaTest • specs2
  • 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. 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. 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 }} """ }
  • 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. 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. 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. Lepsze przykłady • Testowanie interfejsów: forAll { (company: Company) =>
 val output = exportCompany(company)
 
 (output.vatStatus == true)
 
 (company.legalForm == SP_ZOO) ==>
 (output.representation == true)
 }
  • 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)
  • 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. Komu i jak to pomogło?
  • 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. 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. 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
  • 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. 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. 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. 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. Inne języki • JavaScript: QC.js, JSVerify • Clojure: ClojureCheck • Python: Factcheck, Hypothesis • Ruby: Rantly • Mały stopień skomplikowania… napisać samemu?
  • 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. – 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.”