Slide 1

Slide 1 text

PROPERTY - BASED BY SERGIO SASTRE WRITING BULLETPROOF CODE WITH TESTING

Slide 2

Slide 2 text

Example-based testing of your software writing examples of it tests specify the behaviour where by @Gio_Sastre

Slide 3

Slide 3 text

Example-based testing @Gio_Sastre @Test
 fun is_string_palindrome() { val inputString = “1221”
 val revertedInput = inputString.reversed( ) assertTrue(inputString == revertedInput) 
 }

Slide 4

Slide 4 text

Example-based testing @Gio_Sastre @Test
 fun is_string_palindrome() { val inputString = “1221”
 val revertedInput = inputString.reversed( ) assertTrue(inputString == revertedInput) 
 }

Slide 5

Slide 5 text

Example-based testing @Gio_Sastre @Test
 fun is_string_palindrome() { val inputString = “12a21”
 val revertedInput = inputString.reversed( ) assertTrue(inputString == revertedInput) 
 }

Slide 6

Slide 6 text

Example-based testing @Gio_Sastre @Test
 fun is_string_palindrome() { val inputString = “”
 val revertedInput = inputString.reversed( ) assertTrue(inputString == revertedInput) 
 }

Slide 7

Slide 7 text

Text editor @Gio_Sastre Strong password validation

Slide 8

Slide 8 text

@Gio_Sastre Text editor Strong password validation

Slide 9

Slide 9 text

@Gio_Sastre Strong password validation Text editor

Slide 10

Slide 10 text

@Gio_Sastre fun clickSignUpButton() { mutablePasswordError.value = passwordValidator.validate(passwordInput)
 } ViewModel

Slide 11

Slide 11 text

@Gio_Sastre fun clickSignUpButton() { mutablePasswordError.value = passwordValidator.validate(passwordInput)
 } ViewModel

Slide 12

Slide 12 text

@Gio_Sastre The password contains at least 1 digit Show “must contain digits" otherwise 1 at least 1 latin char in upper case. Show “must contain upper case letters” otherwise 2 at least 6 chars. Show “must contain at least 6 chars” otherwise 3 no blanks. Show “must not contain blanks” otherwise 4 at least 1 latin char in lower case. Show “must contain lower case letters” otherwise 5 @Test
 fun passwordValidatorDigitsTest() { assertThat ( passwordValidator.validate(“ABCDEF”) ).contains(“must contain digits”)
 }

Slide 13

Slide 13 text

@Gio_Sastre The password contains at least 1 digit Show “must contain digits" otherwise 1 at least 1 latin char in upper case. Show “must contain upper case letters” otherwise 2 at least 6 chars. Show “must contain at least 6 chars” otherwise 3 no blanks. Show “must not contain blanks” otherwise 4 at least 1 latin char in lower case. Show “must contain lower case letters” otherwise 5 @Test
 fun passwordValidatorDigitsTest() { assertThat ( passwordValidator.validate(“ABCDEF”) ).contains(“must contain digits”)
 assertThat ( passwordValidator.validate(“123456”) ).contains(“must contain upper case letters”) } @Test
 fun passwordValidatorUpperCaseTest() { }

Slide 14

Slide 14 text

@Gio_Sastre The password contains at least 1 digit Show “must contain digits" otherwise 1 at least 1 latin char in upper case. Show “must contain upper case letters” otherwise 2 at least 6 chars. Show “must contain at least 6 chars” otherwise 3 no blanks. Show “must not contain blanks” otherwise 4 at least 1 latin char in lower case. Show “must contain lower case letters” otherwise 5 @ParameterizedTest(
 name = “when password \”{0}\”, error contains \”{1}\”” ) @CsvSource ( “ABCDEF, must contain digits”, “123456, must contain upper case letters” , “12345, must contain at least 6 chars”,
 “12 3 456, must not contain blanks” , “HELLO, must contain lower case letters”, )
 fun passwordValidatorTest ( password: String?, expectedError: String ? ) { assertThat ( passwordValidator.validate(password) ).contains(expectedError) }

Slide 15

Slide 15 text

@Gio_Sastre The password contains at least 1 digit Show “must contain digits" otherwise 1 at least 1 latin char in upper case. Show “must contain upper case letters” otherwise 2 at least 6 chars. Show “must contain at least 6 chars” otherwise 3 no blanks. Show “must not contain blanks” otherwise 4 at least 1 latin char in lower case. Show “must contain lower case letters” otherwise 5 @ParameterizedTest(
 name = “when password \”{0}\”, error contains \”{1}\”” ) @CsvSource ( “ABCDEF, must contain digits”, “123456, must contain upper case letters” , “12345, must contain at least 6 chars”,
 “12 3 456, must not contain blanks” , “HELLO, must contain lower case letters”, )
 fun passwordValidatorTest ( password: String?, expectedError: String ? ) { assertThat ( passwordValidator.validate(password) ).contains(expectedError) }

Slide 16

Slide 16 text

@Gio_Sastre The password contains at least 1 digit Show “must contain digits" otherwise 1 at least 1 latin char in upper case. Show “must contain upper case letters” otherwise 2 at least 6 chars. Show “must contain at least 6 chars” otherwise 3 no blanks. Show “must not contain blanks” otherwise 4 at least 1 latin char in lower case. Show “must contain lower case letters” otherwise 5 @ParameterizedTest(
 name = “when password \”{0}\”, error contains \”{1}\”” ) @CsvSource ( “ABCDEF, must contain digits”, “123456, must contain upper case letters” , “12345, must contain at least 6 chars”,
 “12 3 456, must not contain blanks” , “HELLO, must contain lower case letters”, )
 fun passwordValidatorTest ( password: String?, expectedError: String ? ) { assertThat ( passwordValidator.validate(password) ).contains(expectedError) }

Slide 17

Slide 17 text

@Gio_Sastre The password contains at least 1 digit Show “must contain digits" otherwise 1 at least 1 latin char in upper case. Show “must contain upper case letters” otherwise 2 at least 6 chars. Show “must contain at least 6 chars” otherwise 3 no blanks. Show “must not contain blanks” otherwise 4 at least 1 latin char in lower case. Show “must contain lower case letters” otherwise 5 @ParameterizedTest(
 name = “when password \”{0}\”, error contains \”{1}\”” ) @CsvSource ( “ABCDEF, must contain digits”, “123456, must contain upper case letters” , “12345, must contain at least 6 chars”,
 “12 3 456, must not contain blanks” , “HELLO, must contain lower case letters”, )
 fun passwordValidatorTest ( password: String?, expectedError: String ? ) { assertThat ( passwordValidator.validate(password) ).contains(expectedError) }

Slide 18

Slide 18 text

Are enough? Example-based tests @Gio_Sastre

Slide 19

Slide 19 text

class PasswordValidator(…) { override fun isValid(password: String?): Boolean { … // validate contains upper case letter s if (!password?.contains(“[A-Z]”.toRegex())) { … } … } } @Gio_Sastre

Slide 20

Slide 20 text

class PasswordValidator(…) { override fun isValid(password: String?): Boolean { … // validate contains upper case letter s if (!password?.contains(“[a-z]”.toRegex())) { … } … } } @Gio_Sastre

Slide 21

Slide 21 text

@Gio_Sastre

Slide 22

Slide 22 text

🧐 Upper case: “[a-z]”.toRegex() @Gio_Sastre

Slide 23

Slide 23 text

🧐 @Gio_Sastre Upper case: “[a-z]”.toRegex() show “must contain upper case letters ” if “123456” has no

Slide 24

Slide 24 text

🧐 @Gio_Sastre Lower case: “[a-z]”.toRegex() show “must contain lower case letters ” 🧐 if “123456” has no

Slide 25

Slide 25 text

🧐 @Gio_Sastre Upper case: “[a-z]”.toRegex() if “123456” has no show “must contain upper case letters ” 🤦

Slide 26

Slide 26 text

@Gio_Sastre Upper case: “[a-z]”.toRegex() if “123456” has no show “must contain upper case letters ” 🤦 🧐

Slide 27

Slide 27 text

@Gio_Sastre 🙃 Upper case: “[a-z]”.toRegex() if “abcdef” has no show “must contain upper case letters ”

Slide 28

Slide 28 text

1 example-based tests Problems with 2 Finding good examples @Gio_Sastre Covering all relevant cases

Slide 29

Slide 29 text

Can those issues? we mitigate @Gio_Sastre

Slide 30

Slide 30 text

Example-based testing of your software writing examples of it tests specify the behaviour where by @Gio_Sastre

Slide 31

Slide 31 text

Property-based testing generating thousands of tests specify the behaviour where by @Gio_Sastre semi-random examples of it of your software

Slide 32

Slide 32 text

Example-based testing @Gio_Sastre @Test
 fun is_string_palindrome() { val inputString = “1221”
 val revertedInput = inputString.reversed( ) assertTrue(inputString == revertedInput) 
 }

Slide 33

Slide 33 text

fun is_string_palindrome() { val inputString = SemiRandomString(seed)
 val revertedInput = inputString.reversed( ) assertTrue(inputString == revertedInput) 
 } @Gio_Sastre Property-based testing @Test(tries = 1_000)

Slide 34

Slide 34 text

fun is_string_palindrome() { val inputString = SemiRandomString(seed)
 val revertedInput = inputString.reversed( ) assertTrue(inputString == revertedInput) 
 } @Gio_Sastre Property-based testing @Test(tries = 1_000)

Slide 35

Slide 35 text

@Gio_Sastre Kotlin support Nullables for non-top-level types requires @WithNull Missing features & configurability Previous failing seed not saved, etc. Kotlin multiplatform support Requires IntelliJ plugin to run from IDE Smooth integration Full featured & Configurable via Gradle Stateful testing, statistics, etc. Kotlin support Kotlin Native too Kotlin multiplatform support Jqwik otest

Slide 36

Slide 36 text

@Gio_Sastre Smooth integration Full featured & Configurable via Gradle Kotlin support Kotlin support Stateful testing, statistics, etc. Nullables for non-top-level types requires @WithNull Missing features & configurability Previous failing seed not saved, etc. Kotlin multiplatform support Kotlin Native too Kotlin multiplatform support Requires IntelliJ plugin to run from IDE Jqwik otest

Slide 37

Slide 37 text

@Gio_Sastre Smooth integration Full featured & Configurable via Gradle Kotlin support Stateful testing, statistics, etc. Nullables for non-top-level types requires @WithNull Missing features & configurability Previous failing seed not saved, etc. Kotlin multiplatform support Requires IntelliJ plugin to run from IDE Kotlin support Kotlin Native too Kotlin multiplatform support Jqwik otest

Slide 38

Slide 38 text

@Gio_Sastre Smooth integration Full featured & Configurable via Gradle Kotlin support Stateful testing, statistics, etc. Nullables for non-top-level types requires @WithNull Missing features & configurability Previous failing seed not saved, etc. Kotlin multiplatform support Kotlin support Kotlin Native too Kotlin multiplatform support Requires IntelliJ plugin to run from IDE Jqwik otest

Slide 39

Slide 39 text

@Gio_Sastre Kotlin support Nullables for non-top-level types requires @WithNull Missing features & configurability Previous failing seed not saved, etc. String? Smooth integration Full featured & Configurable via Gradle Stateful testing, statistics, etc. Kotlin support Kotlin Native too Kotlin multiplatform support Requires IntelliJ plugin to run from IDE Jqwik otest

Slide 40

Slide 40 text

@Gio_Sastre Kotlin support Nullables for non-top-level types requires @WithNull Missing features & configurability Previous failing seed not saved, etc. List<@WithNull String> Smooth integration Full featured & Configurable via Gradle Stateful testing, statistics, etc. Kotlin support Kotlin Native too Kotlin multiplatform support Requires IntelliJ plugin to run from IDE Jqwik otest

Slide 41

Slide 41 text

@Gio_Sastre Kotlin support Nullables for non-top-level types requires @WithNull Missing features & configurability Previous failing seed not saved, etc. Kotlin multiplatform support Smooth integration Full featured & Configurable via Gradle Stateful testing, statistics, etc. Kotlin support Kotlin Native too Kotlin multiplatform support Requires IntelliJ plugin to run from IDE Jqwik otest

Slide 42

Slide 42 text

How @Gio_Sastre to write property-based tests?

Slide 43

Slide 43 text

@Gio_Sastre Strong password validation upper case

Slide 44

Slide 44 text

@Gio_Sastre 1 Define the property If password without upper case Show “must contain upper case letters” 3 Test the property Default is 1.000 times 2 Create a generator Password without upper case chars String without upper case chars

Slide 45

Slide 45 text

@Gio_Sastre 1 Define the property If password without upper case Show “must contain upper case letters” fun noUpperCase() = @Provide Arbitraries.strings().ascii( ) .filter { it.matches(“[^A-Z]”.toRegex()) } 3 Test the property Default is 1.000 times 2 Create a generator Password without upper case chars String without upper case chars

Slide 46

Slide 46 text

@Gio_Sastre 1 Define the property If password without upper case Show “must contain upper case letters” 2 Create a generator Password without upper case chars String without upper case chars fun noUpperCase() = @Provide Arbitraries.strings().ascii( ) .filter { it.matches(“[^A-Z]”.toRegex()) } 3 Test the property Default is 1.000 times fun testPasswordValidator ( @ForAll(“noUpperCase”) password: String ? ) { @Property val error = passwordValidator.validate(password ) assertThat(error ) .contains(“must contain upper case letters”) }

Slide 47

Slide 47 text

@Gio_Sastre 1 Define the property If password without upper case Show “must contain upper case letters” 3 Test the property Default is 1.000 times fun testPasswordValidator ( @ForAll(“noUpperCase”) password: String ? ) { @Property(tries = 100) val error = passwordValidator.validate(password ) assertThat(error ) .contains(“must contain upper case letters”) } 2 Create a generator Password without upper case chars String without upper case chars fun noUpperCase() = @Provide Arbitraries.strings().ascii( ) .filter { it.matches(“[^A-Z]”.toRegex()) }

Slide 48

Slide 48 text

@Gio_Sastre 1 Define the property If password without upper case Show “must contain upper case letters” 3 Test the property Default is 1.000 times fun testPasswordValidator ( @ForAll(“noUpperCase”) password: String ? ) { @Property(tries = 100) val error = passwordValidator.validate(password ) assertThat(error ) .contains(“must contain upper case letters”) } @Label(“if password without upper case, error ” + “shows ‘must contain upper case letters’”) 2 Create a generator Password without upper case chars String without upper case chars fun noUpperCase() = @Provide Arbitraries.strings().ascii( ) .filter { it.matches(“[^A-Z]”.toRegex()) }

Slide 49

Slide 49 text

@Gio_Sastre 1 Define the property If password without upper case Show “must contain upper case letters” 3 Test the property Default is 1.000 times 2 Create a generator Password without upper case chars String without upper case chars fun testPasswordValidator ( @ForAll(“noUpperCase”) password: String ? ) { @Property(tries = 100) val error = passwordValidator.validate(password ) assertThat(error ) .contains(“must contain upper case letters”) } @Label(“if password without upper case, error ” + “shows ‘must contain upper case letters’”) fun noUpperCase() = @Provide Arbitraries.strings().ascii( ) .filter { it.matches(“[^A-Z]”.toRegex()) }

Slide 50

Slide 50 text

@Gio_Sastre

Slide 51

Slide 51 text

@Gio_Sastre

Slide 52

Slide 52 text

@Gio_Sastre

Slide 53

Slide 53 text

@Gio_Sastre “漢!字) 𡨸 $&指>事<字9§漢0=漢*字```+#会意@字<指?%” 𡨸 o字9=“ null “漢!字) 𡨸 $&指>事<字9§漢0=漢*字```+#会意@字<指?%” 𡨸 o字9=“

Slide 54

Slide 54 text

@Gio_Sastre Shrinking Trying to find the simplest example that fails for the same reason “漢!字) 𡨸 $&指>事<字9§漢0=漢*字```+#会意@字<指?%” 𡨸 o字9=“ null “漢!字) 𡨸 $&指>事<字9§漢0=漢*字```+#会意@字<指?%” 𡨸 o字9=“

Slide 55

Slide 55 text

@Gio_Sastre “漢!字) 𡨸 $&指>事<字9§漢0=漢*字```+#会意@字<指?%” 𡨸 o字9=“ null “漢!字) 𡨸 $&指>事<字9§漢0=漢*字```+#会意@字<指?%” 𡨸 o字9=“

Slide 56

Slide 56 text

@Gio_Sastre “漢!字) 𡨸 $&指>事<字9§漢0=漢*字```+#会意@字<指?%” 𡨸 o字9=“ null “abc_1234567890“

Slide 57

Slide 57 text

@Gio_Sastre

Slide 58

Slide 58 text

@Gio_Sastre “漢!字) 𡨸 $&指>事<字9§漢0=漢*字```+#会意@字<指?%” 𡨸 o字9=“ null “漢!字) 𡨸 $&指>事<字9§漢0=漢*字```+#会意@字<指?%” 𡨸 o字9=“ null

Slide 59

Slide 59 text

@Gio_Sastre null “漢!字) 𡨸 $&指>事<字9§漢0=漢*字```+#会意@字<指?%” 𡨸 o字9=“ “漢!字) 𡨸 $&指>事<字9§漢0=漢*字```+#会意@字<指?%” 𡨸 o字9=“ null

Slide 60

Slide 60 text

@Gio_Sastre null “漢!字) 𡨸 $&指>事<字9§漢0=漢*字```+#会意@字<指?%” 𡨸 o字9=“ “漢!字) 𡨸 $&指>事<字9§漢0=漢*字```+#会意@字<指?%” 𡨸 o字9=“ fun testPasswordValidator ( @ForAll(“noUpperCase”) password: String ? ) { @Property(tries = 100) val error = passwordValidator.validate(password ) assertThat(error ) .contains(“must contain upper case letters”) } @Label(“if password without upper case, error ” + “shows ‘must contain upper case letters’”) null

Slide 61

Slide 61 text

@Gio_Sastre null “漢!字) 𡨸 $&指>事<字9§漢0=漢*字```+#会意@字<指?%” 𡨸 o字9=“ “漢!字) 𡨸 $&指>事<字9§漢0=漢*字```+#会意@字<指?%” 𡨸 o字9=“ fun testPasswordValidator ( @ForAll(“noUpperCase”) password: String ? ) { @Property(tries = 100) val error = passwordValidator.validate(password ) assertThat(error ) .contains(“must contain upper case letters”) } @Label(“if password without upper case, error ” + “shows ‘must contain upper case letters’”) null

Slide 62

Slide 62 text

@Gio_Sastre null “漢!字) 𡨸 $&指>事<字9§漢0=漢*字```+#会意@字<指?%” 𡨸 o字9=“ “漢!字) 𡨸 $&指>事<字9§漢0=漢*字```+#会意@字<指?%” 𡨸 o字9=“ fun testPasswordValidator ( @ForAll(“noUpperCase”) password: String ? ) { @Property(tries = 100, seed = -3508921172508693187) val error = passwordValidator.validate(password ) assertThat(error ) .contains(“must contain upper case letters”) } @Label(“if password without upper case, error ” + “shows ‘must contain upper case letters’”) null

Slide 63

Slide 63 text

@Gio_Sastre null “漢!字) 𡨸 $&指>事<字9§漢0=漢*字```+#会意@字<指?%” 𡨸 o字9=“ “漢!字) 𡨸 $&指>事<字9§漢0=漢*字```+#会意@字<指?%” 𡨸 o字9=“ No flaky tests Use the seed to reliably reproduce the results of a failing test null

Slide 64

Slide 64 text

@Gio_Sastre null “漢!字) 𡨸 $&指>事<字9§漢0=漢*字```+#会意@字<指?%” 𡨸 o字9=“ “漢!字) 𡨸 $&指>事<字9§漢0=漢*字```+#会意@字<指?%” 𡨸 o字9=“ null

Slide 65

Slide 65 text

@Gio_Sastre null “漢!字) 𡨸 $&指>事<字9§漢0=漢*字```+#会意@字<指?%” 𡨸 o字9=“ “漢!字) 𡨸 $&指>事<字9§漢0=漢*字```+#会意@字<指?%” 𡨸 o字9=“ Total edge cases fun noUpperCase() = @Provide Arbitraries.strings().ascii( ) .filter { it.matches(“[^A-Z]”.toRegex()) } null (jqwik only)

Slide 66

Slide 66 text

@Gio_Sastre null “漢!字) 𡨸 $&指>事<字9§漢0=漢*字```+#会意@字<指?%” 𡨸 o字9=“ “漢!字) 𡨸 $&指>事<字9§漢0=漢*字```+#会意@字<指?%” 𡨸 o字9=“ fun noUpperCase() = @Provide Arbitraries.strings().ascii( ) .filter { it.matches(“[^A-Z]”.toRegex()) } noUpperCase().edgeCases() null (Java.lang.Character.MIN_VALUE) “/u0000” “” (Empty char) “ ” (Space char) Total edge cases (jqwik only)

Slide 67

Slide 67 text

1 Property-based testing main features 2 Shrinking (minimum failing sample) Reproduce failing tests (seeds) 3 Tests for edge cases @Gio_Sastre

Slide 68

Slide 68 text

What else @Gio_Sastre can Jqwik do?

Slide 69

Slide 69 text

@Gio_Sastre Stateful testing Text editor

Slide 70

Slide 70 text

@Gio_Sastre Model val currentText: String? val undoActions: List val redoActions: List data class TextEditor ( )

Slide 71

Slide 71 text

@Gio_Sastre Actions on model 1 fun changeTextTo(newText: String?) { … } 2 fun undo() { … } 3 fun redo() { … }

Slide 72

Slide 72 text

(if something to redo) (if something to undo) @Gio_Sastre 1 On “text change” one more “undo” action possible, max 20 no “redo” actions possible 2 On “undo” one more “redo” action possible, max 20 next “redo” brings the previous text back 3 On “redo” one more “undo” action possible, max 20 next “undo” brings the previous text back Requirements

Slide 73

Slide 73 text

(if something to redo) (if something to undo) @Gio_Sastre 1 On “text change” one more “undo” action possible, max 20 no “redo” actions possible 2 On “undo” one more “redo” action possible, max 20 next “redo” brings the previous text back 3 On “redo” one more “undo” action possible, max 20 next “undo” brings the previous text back Requirements

Slide 74

Slide 74 text

(if something to redo) @Gio_Sastre 1 On “text change” one more “undo” action possible, max 20 no “redo” actions possible 2 On “undo” one more “redo” action possible, max 20 next “redo” brings the previous text back 3 On “redo” one more “undo” action possible, max 20 next “undo” brings the previous text back Requirements (if something to undo)

Slide 75

Slide 75 text

(if something to undo) @Gio_Sastre 1 On “text change” one more “undo” action possible, max 20 no “redo” actions possible 2 On “undo” one more “redo” action possible, max 20 next “redo” brings the previous text back 3 On “redo” one more “undo” action possible, max 20 next “undo” brings the previous text back Requirements (if something to redo)

Slide 76

Slide 76 text

@Gio_Sastre 1 On “text change” one more “undo” action possible, max 20 no “redo” actions possible 2 On “undo” one more “redo” action possible, max 20 next “redo” brings the previous text back 3 On “redo” one more “undo” action possible, max 20 next “undo” brings the previous text back Requirements (if something to undo) (if something to redo)

Slide 77

Slide 77 text

@Gio_Sastre 1 On “text change” one more “undo” action possible, max 20 no “redo” actions possible 2 On “undo” one more “redo” action possible, max 20 next “redo” brings the previous text back 3 On “redo” one more “undo” action possible, max 20 next “undo” brings the previous text back Requirements (if something to undo) (if something to redo)

Slide 78

Slide 78 text

@Gio_Sastre 1 On “text change” one more “undo” action possible, max 20 no “redo” actions possible fun onTextChange_max_undo_actions_is_20() { @Test val editor = TextEditor( ) repeat(21){ editor.textChange(“abc”) } assertThat(editor.undoActions).size(20) } Requirements

Slide 79

Slide 79 text

@Gio_Sastre 1 On “text change” one more “undo” action possible, max 20 no “redo” actions possible fun onTextChange_max_undo_actions_is_20() { @Test val editor = TextEditor( ) repeat(21){ editor.textChange(“abc”) } assertThat(editor.undoActions).size(20) } Requirements

Slide 80

Slide 80 text

@Gio_Sastre 1 On “text change” one more “undo” action possible, max 20 no “redo” actions possible

Slide 81

Slide 81 text

@Gio_Sastre 1 On “text change” one more “undo” action possible, max 20 no “redo” actions possible class TextChange(val text: String?): Action { override fun run(editor: TextEditor) { val previousUndoCount = editor.undoActionsCount( ) editor.changeTextTo(text ) assertThat(editor ) .undoActionsEquals(min(previousUndoCount + 1, 20) ) .redoActionsEquals(0 ) } }

Slide 82

Slide 82 text

@Gio_Sastre 1 On “text change” one more “undo” action possible, max 20 no “redo” actions possible class TextChange(val text: String?): Action { override fun run(editor: TextEditor) { val previousUndoCount = editor.undoActionsCount( ) editor.changeTextTo(text ) assertThat(editor ) .undoActionsEquals(min(previousUndoCount + 1, 20) ) .redoActionsEquals(0 ) } }

Slide 83

Slide 83 text

class TextChange(val text: String?): Action { override fun run(editor: TextEditor) { val previousUndoCount = editor.undoActionsCount( ) editor.changeTextTo(text ) assertThat(editor ) .undoActionsEquals(min(previousUndoCount + 1, 20) ) .redoActionsEquals(0 ) } } @Gio_Sastre 1 On “text change” one more “undo” action possible, max 20 no “redo” actions possible

Slide 84

Slide 84 text

@Gio_Sastre class TextChange(val text: String?): Action { override fun run(editor: TextEditor) { val previousUndoCount = editor.undoActionsCount( ) editor.changeTextTo(text ) assertThat(editor ) .undoActionsEquals(min(previousUndoCount + 1, 20) ) .redoActionsEquals(0 ) } } 1 On “text change” one more “undo” action possible, max 20 no “redo” actions possible

Slide 85

Slide 85 text

@Gio_Sastre class TextChange(val text: String?): Action { override fun run(editor: TextEditor) { val previousUndoCount = editor.undoActionsCount( ) editor.changeTextTo(text ) assertThat(editor ) .undoActionsEquals(min(previousUndoCount + 1, 20) ) .redoActionsEquals(0 ) } } 1 On “text change” one more “undo” action possible, max 20 no “redo” actions possible

Slide 86

Slide 86 text

@Gio_Sastre 2 On “undo” one more “redo” action possible, max 20 next “redo” brings the previous text back (if something to undo)

Slide 87

Slide 87 text

@Gio_Sastre 2 On “undo” one more “redo” action possible, max 20 next “redo” brings the previous text back (if something to undo) class Undo(): Action { override fun run(editor: TextEditor) { val preEditor = editor.copy( ) editor.undo( ) assertThat(editor ) .redoActionsEquals(min(preEditor.redoActions() + 1, 20) ) .nextRedoTextEquals(preEditor.text ) } } override fun precondition(editor: TextEditor) = editor.undoActionsCount() > 0

Slide 88

Slide 88 text

@Gio_Sastre 2 On “undo” one more “redo” action possible, max 20 next “redo” brings the previous text back (if something to undo) class Undo(): Action { override fun run(editor: TextEditor) { val preEditor = editor.copy( ) editor.undo( ) assertThat(editor ) .redoActionsEquals(min(preEditor.redoActions() + 1, 20) ) .nextRedoTextEquals(preEditor.text ) } } override fun precondition(editor: TextEditor) = editor.undoActionsCount() > 0

Slide 89

Slide 89 text

@Gio_Sastre 2 On “undo” one more “redo” action possible, max 20 next “redo” brings the previous text back (if something to undo) class Undo(): Action { override fun run(editor: TextEditor) { val preEditor = editor.copy( ) editor.undo( ) assertThat(editor ) .redoActionsEquals(min(preEditor.redoActions() + 1, 20) ) .nextRedoTextEquals(preEditor.text ) } } override fun precondition(editor: TextEditor) = editor.undoActionsCount() > 0

Slide 90

Slide 90 text

fun randomTextChange() = Arbitraries.strings().map { TextChange(it) } fun undo() = Arbitraries.just(Undo()) fun redo() = Arbitraries.just(Redo()) @Gio_Sastre Semi-random action generator fun textEditorActionGenerator() = @Provide Arbitraries.sequences(randomActions()) fun randomActions() = Arbitraries.oneOf(randomTextChange(), undo(), redo())

Slide 91

Slide 91 text

@Gio_Sastre Semi-random action generator fun textEditorActionGenerator() = @Provide Arbitraries.sequences(randomActions()) fun randomActions() = Arbitraries.oneOf(randomTextChange(), undo(), redo()) fun randomTextChange() = Arbitraries.strings().map { TextChange(it) } fun undo() = Arbitraries.just(Undo()) fun redo() = Arbitraries.just(Redo())

Slide 92

Slide 92 text

@Gio_Sastre Semi-random action generator fun textEditorActionGenerator() = @Provide Arbitraries.sequences(randomActions()) fun randomActions() = Arbitraries.oneOf(randomTextChange(), undo(), redo()) fun randomTextChange() = Arbitraries.strings().map { TextChange(it) } fun undo() = Arbitraries.just(Undo()) fun redo() = Arbitraries.just(Redo())

Slide 93

Slide 93 text

@Gio_Sastre Semi-random action generator fun textEditorActionGenerator() = @Provide Arbitraries.sequences(randomActions()) fun randomActions() = Arbitraries.oneOf(randomTextChange(), undo(), redo()) fun randomTextChange() = Arbitraries.strings().map { TextChange(it) } fun undo() = Arbitraries.just(Undo()) fun redo() = Arbitraries.just(Redo())

Slide 94

Slide 94 text

@Gio_Sastre Semi-random action generator fun textEditorActionGenerator() = @Provide Arbitraries.sequences(randomActions()) fun randomActions() = Arbitraries.oneOf(randomTextChange(), undo(), redo()) fun randomTextChange() = Arbitraries.strings().map { TextChange(it) } fun undo() = Arbitraries.just(Undo()) fun redo() = Arbitraries.just(Redo())

Slide 95

Slide 95 text

@Gio_Sastre fun randomActions() = Arbitraries.oneOf(randomTextChange(), undo(), redo()) fun textEditorActionGenerator() = @Provide Arbitraries.sequences(randomActions()) Property test fun statefulTextEditorTest ( @ForAll(“textEditorActionGenerator”) actions: ActionSequence ) = actions.run(TextEditor()) @Property fun randomTextChange() = Arbitraries.strings().map { TextChange(it) } fun undo() = Arbitraries.just(Undo()) fun redo() = Arbitraries.just(Redo()) class TextChange(val text: String?): Action { … } class Undo(): Action { … } class Redo(): Action { … }

Slide 96

Slide 96 text

@Gio_Sastre class TextChange(val text: String?): Action { … } class Undo(): Action { … } class Redo(): Action { … } fun statefulTextEditorTest ( @ForAll(“textEditorActionGenerator”) actions: ActionSequence ) = actions.run(TextEditor()) @Property fun textEditorActionGenerator() = @Provide Arbitraries.sequences(randomActions()) Property test

Slide 97

Slide 97 text

1 Property-based stateful testing 2 Identify actions Preconditions in actions 3 Assertions in actions @Gio_Sastre with Jqwik

Slide 98

Slide 98 text

@Gio_Sastre pros & cons Property-based testing of

Slide 99

Slide 99 text

Cons @Gio_Sastre Pros @Gio_Sastre

Slide 100

Slide 100 text

Cons @Gio_Sastre Pros @Gio_Sastre More robust tests Cover edge cases we might forget Random but deterministic tests

Slide 101

Slide 101 text

Cons Hard to find properties Properties are not always sufficient @Gio_Sastre More robust tests Cover edge cases we might forget Random but deterministic tests Pros @Gio_Sastre Run more tests, so run more slowly

Slide 102

Slide 102 text

Cons Hard to find properties @Gio_Sastre @Gio_Sastre fun String?.reversed() : String? { … } Create method that reverses a given string Properties are not always sufficient Run more tests, so run more slowly

Slide 103

Slide 103 text

Cons Hard to find properties @Gio_Sastre @Gio_Sastre Patterns to find properties Business rule as property If password contains blanks show error message… fun String?.reversed() : String? { … } Create method that reverses a given string Properties are not always sufficient Run more tests, so run more slowly

Slide 104

Slide 104 text

Cons @Gio_Sastre @Gio_Sastre Patterns to find properties Test Oracle list.bubbleSort() == list.moreEfficientSort() fun String?.reversed() : String? { … } Create method that reverses a given string Hard to find properties Properties are not always sufficient Run more tests, so run more slowly

Slide 105

Slide 105 text

Cons @Gio_Sastre @Gio_Sastre Invariant functions Patterns to find properties string.reverse()?.length == string?.length fun String?.reversed() : String? { … } Create method that reverses a given string Hard to find properties Properties are not always sufficient Run more tests, so run more slowly

Slide 106

Slide 106 text

Cons @Gio_Sastre @Gio_Sastre fun String?.reversed() : String? { … } @Propert y fun reversedStringKeepsLength ( @ForAll string: String ? ) { assertTrue ( string.reversed()?.length == string?.lengt h ) } Create method that reverses a given string Hard to find properties Properties are not always sufficient Run more tests, so run more slowly

Slide 107

Slide 107 text

Cons @Gio_Sastre @Gio_Sastre Inverse functions Patterns to find properties string.reversed().reversed() == string @Propert y fun reversedStringKeepsLength ( @ForAll string: String ? ) { assertTrue ( string.reversed()?.length == string?.lengt h ) } fun String?.reversed() : String? { … } Create method that reverses a given string Hard to find properties Properties are not always sufficient Run more tests, so run more slowly

Slide 108

Slide 108 text

Cons @Gio_Sastre @Gio_Sastre @Propert y fun reversedStringKeepsLength ( @ForAll string: String ? ) { assertTrue ( string.reversed()?.length == string?.lengt h ) } @Propert y fun reversedStringTwiceReturnsOriginal ( @ForAll string: String ? ) { assertTrue ( string.reversed().reversed() == string ) } fun String?.reversed() : String? { … } Create method that reverses a given string Hard to find properties Properties are not always sufficient Run more tests, so run more slowly

Slide 109

Slide 109 text

Cons @Gio_Sastre @Gio_Sastre @Propert y fun reversedStringKeepsLength ( @ForAll string: String ? ) { assertTrue ( string.reversed()?.length == string?.lengt h ) } @Propert y fun reversedStringTwiceReturnsOriginal ( @ForAll string: String ? ) { assertTrue ( string.reversed().reversed() == string ) } fun String?.reversed() : String? { … } Create method that reverses a given string Hard to find properties Properties are not always sufficient Run more tests, so run more slowly

Slide 110

Slide 110 text

Cons @Gio_Sastre @Gio_Sastre @Propert y fun reversedStringKeepsLength ( @ForAll string: String ? ) { assertTrue ( string.reversed()?.length == string?.lengt h ) } @Propert y fun reversedStringTwiceReturnsOriginal ( @ForAll string: String ? ) { assertTrue ( string.reversed().reversed() == string ) } fun String?.reversed() : String? = this Create method that reverses a given string Hard to find properties Properties are not always sufficient Run more tests, so run more slowly

Slide 111

Slide 111 text

Cons @Gio_Sastre @Gio_Sastre @Propert y fun reversedStringKeepsLength ( @ForAll string: String ? ) { assertTrue ( string.reversed()?.length == string?.lengt h ) } @Propert y fun reversedStringTwiceReturnsOriginal ( @ForAll string: String ? ) { assertTrue ( string.reversed().reversed() == string ) } fun String?.reversed() : String? = this this this Create method that reverses a given string Hard to find properties Properties are not always sufficient Run more tests, so run more slowly

Slide 112

Slide 112 text

Cons @Gio_Sastre @Gio_Sastre @Propert y fun reversedStringKeepsLength ( @ForAll string: String ? ) { assertTrue ( string.reversed()?.length == string?.lengt h ) } @Propert y fun reversedStringTwiceReturnsOriginal ( @ForAll string: String ? ) { assertTrue ( string.reversed().reversed() == string ) } fun String?.reversed() : String? = this this this Create method that reverses a given string Hard to find properties Properties are not always sufficient Run more tests, so run more slowly

Slide 113

Slide 113 text

Cons @Gio_Sastre @Gio_Sastre Create method that reverses a given string @Propert y fun reversedStringKeepsLength ( @ForAll string: String ? ) { assertTrue ( string.reversed()?.length == string?.lengt h ) } @Propert y fun reversedStringTwiceReturnsOriginal ( @ForAll string: String ? ) { assertTrue ( string.reversed().reversed() == string ) } fun String?.reversed() : String? = this this this Hard to find properties Properties are not always sufficient Run more tests, so run more slowly

Slide 114

Slide 114 text

Cons @Gio_Sastre @Gio_Sastre @Propert y fun reversedStringKeepsLength ( @ForAll string: String ? ) { assertTrue ( string.reversed()?.length == string?.lengt h ) } @Propert y fun reversedStringTwiceReturnsOriginal ( @ForAll string: String ? ) { assertTrue ( string.reversed().reversed() == string ) } fun String?.reversed() : String? { … } Create method that reverses a given string Hard to find properties Properties are not always sufficient Run more tests, so run more slowly

Slide 115

Slide 115 text

Cons @Gio_Sastre @Gio_Sastre @Property(times = 100 ) fun reversedStringKeepsLength ( @ForAll string: String ? ) { assertTrue ( string.reversed()?.length == string?.lengt h ) } @Property(times = 100 ) fun reversedStringTwiceReturnsOriginal ( @ForAll string: String ? ) { assertTrue ( string.reversed().reversed() == string ) } fun String?.reversed() : String? { … } Create method that reverses a given string Hard to find properties Properties are not always sufficient Run more tests, so run more slowly

Slide 116

Slide 116 text

1 Property-based testing (PBT) opinionated advice 2 Example + property tests PBT for widely used code 3 @Gio_Sastre PBT for obvious properties

Slide 117

Slide 117 text

attention for your THANKS @Gio_Sastre

Slide 118

Slide 118 text

@Gio_Sastre code @sergio-sastre Sergio Sastre Flórez Lead & Senior Android developer appdev.de blogs @SergioSastre sergiosastre.hashnode.dev