Slide 1

Slide 1 text

on Android Modern Testing Jolanda Verhoef Developer Relations Engineer Google she/her 
 @lojanda Jose Alcérreca Developer Relations Engineer Google he/him 
 @ppvi

Slide 2

Slide 2 text

Agenda ANSI/IEEE 1059 A process of a software item analyzing in order to detect the discrepancies between actual and required conditions (that is errors/bugs/defects) and to estimate the software item features. ● Business process-based testing ● Operational profile testing ● Requirements-based testing ● Incident management tools ● Non-functional test design techniques ● Glass box testing

Slide 3

Slide 3 text

Horror stories Modern Testing Strategies Regressions Manual QA Unit tests UI tests Screenshots Sync issues Using fakes Where to start

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

No content

Slide 9

Slide 9 text

No content

Slide 10

Slide 10 text

Horror story I

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

fun isValid(emailAddress: String): Boolean { return Pattern.compile( "(?:[a-z0-9!#\$%&'*+/=?^_`{|}~-]+" + "(?:\\.[a-z0-9!#\$%&'*+/=?^_`{|}~-]+)*" + "|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\" + "\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@" + "(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+" + "[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:" + "(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}" + "(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:" + "[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\" + "\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])" ).matcher(emailAddress).find() } Email validator

Slide 13

Slide 13 text

No content

Slide 14

Slide 14 text

two months later...

Slide 15

Slide 15 text

Germany Spain UK Netherlands

Slide 16

Slide 16 text

Blimey! I can't log into the app. Rubbish. D. Turner July 6th, 2022

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

fun isValid(emailAddress: String): Boolean { return Pattern.compile( "(?:[a-z0-9!#\$%&'*+/=?^_`{|}~-]+" + "(?:\\.[a-z0-9!#\$%&'*+/=?^_`{|}~-]+)*" + "|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\" + "\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@" + "(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+" + "[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:" + "(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}" + "(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:" + "[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\" + "\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])" ).matcher(emailAddress).find() } Email validator fun isValid(emailAddress: String): Boolean { return Pattern.compile( "(?:[a-z0-9!#\$%&'*+/=?^_`{|}~-]+" + "(?:\\.[a-z0-9!#\$%&'*+/=?^_`{|}~-]+)*" + "|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\" + "\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@" + "(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+" + "[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:" + "(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}" + "(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:" + "[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\" + "\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])" ).matcher(emailAddress).find() }

Slide 19

Slide 19 text

three days later...

Slide 20

Slide 20 text

1 2 3 4 5 Jul 2 Jul 3 Jul 4 Jul 5 Jul 6 Google Play Rating

Slide 21

Slide 21 text

Can’t log in :( TJ Dahunsi July 9th, 2022

Slide 22

Slide 22 text

REGRESSION

Slide 23

Slide 23 text

Design Implementation QA Production Cost of a bug

Slide 24

Slide 24 text

class EmailValidatorTest { @Test fun emailAddress_default_isValid() { assertTrue(EmailValidator().isValid("[email protected]")) } @Test fun emailAddress_withDotsInLocalPart_isValid() { assertTrue(EmailValidator().isValid("[email protected]")) } @Test fun emailAddress_withoutDomain_isInvalid() { assertFalse(EmailValidator().isValid("person@example")) } }

Slide 25

Slide 25 text

class EmailValidatorTest { @Test fun emailAddress_default_isValid() { assertTrue(EmailValidator().isValid("[email protected]")) } @Test fun emailAddress_withDotsInLocalPart_isValid() { assertTrue(EmailValidator().isValid("[email protected]")) } @Test fun emailAddress_withoutDomain_isInvalid() { assertFalse(EmailValidator().isValid("person@example")) } @Test fun emailAddress_withMultiplePartsDomain_isValid() { assertTrue(EmailValidator().isValid("[email protected]")) } }

Slide 26

Slide 26 text

Horror story II

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

OK Vale Bestätigung

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

Testing strategies

Slide 37

Slide 37 text

Local tests Instrumented tests vs

Slide 38

Slide 38 text

Local tests JVM Tests

Slide 39

Slide 39 text

Local tests JVM T

Slide 40

Slide 40 text

Instrumented tests Tests App code

Slide 41

Slide 41 text

Instrumented tests Tests App APK

Slide 42

Slide 42 text

Instrumented tests Tests APK Test APK

Slide 43

Slide 43 text

Instrumented tests Tests APK Test APK

Slide 44

Slide 44 text

Local Instrumented Smaller, faster Bigger, higher fidelity Integration tests Semantic UI tests Screenshot UI tests Performance tests Accessibility tests Unit tests

Slide 45

Slide 45 text

Local Instrumented Smaller, faster Bigger, higher fidelity Integration tests Semantic UI tests Screenshot UI tests Performance tests Accessibility tests Unit tests

Slide 46

Slide 46 text

Email validator App

Slide 47

Slide 47 text

Email validator Test: person@example [email protected] [email protected]

Slide 48

Slide 48 text

class EmailValidatorTest { @Test fun emailAddress_default_isValid() { assertTrue(EmailValidator().isValid("[email protected]")) } }

Slide 49

Slide 49 text

class EmailValidatorTest { @Test fun emailAddress_default_isValid() { assertTrue(EmailValidator().isValid("[email protected]")) } @Test fun emailAddress_withDotsInLocalPart_isValid() { assertTrue(EmailValidator().isValid("[email protected]")) } @Test fun emailAddress_withoutDomain_isInvalid() { assertFalse(EmailValidator().isValid("person@example")) } @Test fun emailAddress_withMultiplePartsDomain_isValid() { assertTrue(EmailValidator().isValid("[email protected]")) } }

Slide 50

Slide 50 text

Email validator App LoginViewModel

Slide 51

Slide 51 text

Email validator LoginViewModel

Slide 52

Slide 52 text

Email validator Fake Email validator LoginViewModel

Slide 53

Slide 53 text

Fakes or Mocks?

Slide 54

Slide 54 text

Email validator LoginViewModel Fake Email validator Test: Login is successful

Slide 55

Slide 55 text

class FakeEmailValidator(val alwaysValid: Boolean): EmailValidator { override fun isValid(emailAddress: String): Boolean { return alwaysValid } }

Slide 56

Slide 56 text

class LoginViewModelTest { @Test fun login_withValidEmail_returnsValidEmailUIState() { val viewModel = LoginViewModel( FakeEmailValidator(alwaysValid = true) ) } }

Slide 57

Slide 57 text

class LoginViewModelTest { @Test fun login_withValidEmail_returnsValidEmailUIState() { val viewModel = LoginViewModel( FakeEmailValidator(alwaysValid = true) ) viewModel.login(email = "whatevs", passwd = "hunter2") Assert.assertTrue(viewModel.uiState.validEmail) } }

Slide 58

Slide 58 text

App AuthManager

Slide 59

Slide 59 text

AuthManager

Slide 60

Slide 60 text

AuthManager

Slide 61

Slide 61 text

Fake Notification Manager? AuthManager

Slide 62

Slide 62 text

Robolectric ? AuthManager

Slide 63

Slide 63 text

Local Instrumented Smaller, faster Bigger, higher fidelity Semantic UI tests Screenshot UI tests Performance tests Accessibility tests Unit tests Integration tests

Slide 64

Slide 64 text

Local Instrumented Smaller, faster Bigger, higher fidelity Semantic UI tests Screenshot UI tests Performance tests Accessibility tests Unit tests Integration tests

Slide 65

Slide 65 text

App

Slide 66

Slide 66 text

App System under test Library

Slide 67

Slide 67 text

System under test Library

Slide 68

Slide 68 text

MainActivity Navigation Library

Slide 69

Slide 69 text

MainActivity Navigation Library Test Navigation Library

Slide 70

Slide 70 text

UserRepository Room in-memory DB MainActivity Navigation Library Test Navigation Library

Slide 71

Slide 71 text

d.android.com/kotlin/coroutines/test

Slide 72

Slide 72 text

API 27 3.19.4 API 28 3.22.0 API 30 3.28.0 API 33 3.32.2 Integration test: SQLite compatibility

Slide 73

Slide 73 text

Local Instrumented Smaller, faster Bigger, higher fidelity Screenshot UI tests Performance tests Accessibility tests Unit tests Integration tests Semantic UI tests

Slide 74

Slide 74 text

Local Instrumented Smaller, faster Bigger, higher fidelity Screenshot UI tests Performance tests Accessibility tests Unit tests Integration tests Semantic UI tests

Slide 75

Slide 75 text

Render UI

Slide 76

Slide 76 text

Render UI Validate properties is enabled

Slide 77

Slide 77 text

Render UI Interact Click! Validate properties

Slide 78

Slide 78 text

Render UI Validate properties Interact is disabled error shows

Slide 79

Slide 79 text

Render UI Validate properties Interact is disabled error shows

Slide 80

Slide 80 text

Small • Render part of UI • Verify properties Large • Render app • Verify properties • Interact • Verify properties • Interact • Verify properties • Interact • Verify properties Medium • Render one screen • Verify properties • Interact • Verify properties

Slide 81

Slide 81 text

Small • Render part of UI • Verify properties

Slide 82

Slide 82 text

Medium • Render one screen • Verify properties • Interact • Verify properties

Slide 83

Slide 83 text

Large • Render app • Verify properties • Interact • Verify properties • Interact • Verify properties • Interact • Verify properties

Slide 84

Slide 84 text

Compose View system test APIs Espresso

Slide 85

Slide 85 text

class LoginScreenTest { @get:Rule val composeTestRule = createComposeRule() @Test fun invalidEmail_disablesButton() { //.. } }

Slide 86

Slide 86 text

class LoginScreenTest { @get:Rule val composeTestRule = createComposeRule() @Test fun invalidEmail_disablesButton() { composeTestRule.setContent { MySootheTheme { LoginScreen() } } } }

Slide 87

Slide 87 text

val composeTestRule = createComposeRule() @Test fun invalidEmail_disablesButton() { composeTestRule.setContent { MySootheTheme { LoginScreen() } } composeTestRule .onNodeWithText("LOG IN") .assertIsEnabled() } }

Slide 88

Slide 88 text

composeTestRule.setContent { MySootheTheme { LoginScreen() } } composeTestRule .onNodeWithText("LOG IN") .assertIsEnabled() composeTestRule .onNodeWithText("Email address") .performTextInput("person@example") } }

Slide 89

Slide 89 text

} composeTestRule .onNodeWithText("LOG IN") .assertIsEnabled() composeTestRule .onNodeWithText("Email address") .performTextInput("person@example") composeTestRule .onNodeWithText("LOG IN") .assertIsNotEnabled() } }

Slide 90

Slide 90 text

class LoginScreenTest { @get:Rule val composeTestRule = createComposeRule() @Test fun invalidEmail_disablesButton() { composeTestRule.setContent { MySootheTheme { LoginScreen() } } composeTestRule .onNodeWithText("LOG IN") .assertIsEnabled() // src/androidTest/java/com/example/soothe

Slide 91

Slide 91 text

class LoginScreenTest { @get:Rule val composeTestRule = createComposeRule() @Test fun invalidEmail_disablesButton() { composeTestRule.setContent { MySootheTheme { LoginScreen() } } composeTestRule .onNodeWithText("LOG IN") .assertIsEnabled() // src/test/java/com/example/soothe @RunWith(RobolectricTestRunner::class)

Slide 92

Slide 92 text

Local Instrumented Integration tests Performance tests Accessibility tests Semantic UI tests Screenshot UI tests Unit tests Smaller, faster Bigger, higher fidelity Robolectric Compose Test APIs Espresso

Slide 93

Slide 93 text

Local Instrumented Smaller, faster Bigger, higher fidelity Performance tests Accessibility tests Unit tests Integration tests Semantic UI tests Screenshot UI tests

Slide 94

Slide 94 text

// Espresso onView(withBackgroundColor(Color.Red)).perform(click())

Slide 95

Slide 95 text

// Espresso onView(withBackgroundColor(Color.Red)).perform(click()) // Compose? onNode(hasBackgroundColor(Color.Red)).performClick()

Slide 96

Slide 96 text

@Composable fun MyCard() { Surface( color = Color.Red, modifier = Modifier.testTag("Green background") // Lies ) { } }

Slide 97

Slide 97 text

@Composable fun MyCard() { Surface( color = Color.Red, modifier = Modifier.testTag("Green background") // Lies ) { } } // LoginScreenTest.kt @Test fun loginButton_onStart_backgroundIsGreen() { composeTestRule.onNodeWithTag("Green background") }

Slide 98

Slide 98 text

@Composable fun MyCard() { Surface( color = Color.Red, modifier = Modifier.semantics { customBackgroundProperty = Color.Green // Lies } ) { } }

Slide 99

Slide 99 text

Button( onClick = {}, modifier = Modifier ) { Text("LOG IN") }

Slide 100

Slide 100 text

Button( onClick = {}, modifier = Modifier .fillMaxWidth() ) { Text("LOG IN") }

Slide 101

Slide 101 text

Golden screenshot New screenshot Difference

Slide 102

Slide 102 text

No content

Slide 103

Slide 103 text

Local Instrumented Smaller, faster Bigger, higher fidelity Screenshot UI tests Performance tests Accessibility tests Unit tests Integration tests Semantic UI tests Compatibility testing

Slide 104

Slide 104 text

No content

Slide 105

Slide 105 text

github.com/pedrovgs/Shot github.com/dropbox/dropshots

Slide 106

Slide 106 text

class TopicScreenScreenshotTests { @get:Rule val composeTestRule = createAndroidComposeRule() @Test fun test1() { composeTestRule.setContent { LoginScreen() } } }

Slide 107

Slide 107 text

class TopicScreenScreenshotTests { @get:Rule val composeTestRule = createAndroidComposeRule() @get:Rule val dropshots = Dropshots() @Test fun test1() { composeTestRule.setContent { LoginScreen() } } }

Slide 108

Slide 108 text

class TopicScreenScreenshotTests { @get:Rule val composeTestRule = createAndroidComposeRule() @get:Rule val dropshots = Dropshots() @Test fun test1() { composeTestRule.setContent { LoginScreen() } dropshots.assertSnapshot(composeTestRule.activity, "test1") } }

Slide 109

Slide 109 text

Local Instrumented Smaller, faster Bigger, higher fidelity Screenshot UI tests Performance tests Accessibility tests Unit tests Integration tests Semantic UI tests Compatibility testing

Slide 110

Slide 110 text

Local Instrumented Smaller, faster Bigger, higher fidelity Screenshot UI tests Performance tests Accessibility tests Unit tests Integration tests Semantic UI tests Regression testing

Slide 111

Slide 111 text

github.com/cashapp/paparazzi/

Slide 112

Slide 112 text

Sync issues

Slide 113

Slide 113 text

Email validator App LoginViewModel

Slide 114

Slide 114 text

Email validator App LoginViewModel Server

Slide 115

Slide 115 text

No content

Slide 116

Slide 116 text

@Test fun invalidEmail_disablesButton() { composeTestRule .onNodeWithText("LOG IN") .assertIsEnabled() composeTestRule .onNodeWithText("Email address") .performTextInput("person@example") composeTestRule .onNodeWithText("LOG IN") .assertIsNotEnabled() } }

Slide 117

Slide 117 text

IDLE IDLE BUSY BUSY IDLE ?

Slide 118

Slide 118 text

1. Idling Resources 2. Waiting for things 3. Replace components 1. Idling Resources class AuthRepository(val authService: AuthService) { suspend fun login(email: String, password: String) { authService.login(email, password) } }

Slide 119

Slide 119 text

1. Idling Resources 2. Waiting for things 3. Replace components 1. Idling Resources class AuthRepository(val authService: AuthService) { private val idlingResource = SimpleIdlingResource() suspend fun login(email: String, password: String) { authService.login(email, password) } }

Slide 120

Slide 120 text

1. Idling Resources 2. Waiting for things 3. Replace components 1. Idling Resources class AuthRepository(val authService: AuthService) { private val idlingResource = SimpleIdlingResource() suspend fun login(email: String, password: String) { idlingResource.setIdleState(false) authService.login(email, password) idlingResource.setIdleState(true) } }

Slide 121

Slide 121 text

1. Idling Resources 2. Waiting for things 3. Replace components 1. Idling Resources class AuthRepository(val authService: AuthService) { private val idlingResource = SimpleIdlingResource() suspend fun login(email: String, password: String) { idlingResource.setIdleState(false) authService.login(email, password) idlingResource.setIdleState(true) } } goo.gle/espresso-idling-resources

Slide 122

Slide 122 text

1. Idling Resources 2. Waiting for things 3. Replace components 1. Idling Resources

Slide 123

Slide 123 text

1. Idling Resources 2. Waiting for things 3. Replace components 2. Waiting for things composeTestRule .onNodeWithText("LOG IN") .performClick()

Slide 124

Slide 124 text

1. Idling Resources 2. Waiting for things 3. Replace components 2. Waiting for things composeTestRule .onNodeWithText("LOG IN") .performClick() // wait... composeTestRule .onNodeWithText("LOG IN") .assertIsNotEnabled()

Slide 125

Slide 125 text

1. Idling Resources 2. Waiting for things 3. Replace components 2. Waiting for things composeTestRule .onNodeWithText("LOG IN") .performClick() Thread.sleep(2000) // don't do this composeTestRule .onNodeWithText("LOG IN") .assertIsNotEnabled()

Slide 126

Slide 126 text

1. Idling Resources 2. Waiting for things 3. Replace components 2. Waiting for things composeTestRule .onNodeWithText("LOG IN") .performClick() // Wait until there's one element with a "LOG IN" text composeTestRule.waitUntil { composeTestRule .onAllNodesWithText("LOG IN") .fetchSemanticsNodes().size == 1 } composeTestRule .onNodeWithText("LOG IN") .assertIsNotEnabled()

Slide 127

Slide 127 text

1. Idling Resources 2. Waiting for things 3. Replace components 2. Waiting for things composeTestRule .onNodeWithText("LOG IN") .performClick() composeTestRule.waitUntilExists(hasText("LOG IN")) composeTestRule .onNodeWithText("LOG IN") .assertIsNotEnabled() goo.gle/compose-test-sync

Slide 128

Slide 128 text

1. Idling Resources 2. Waiting for things 3. Replace components 3. Replace components Server Email validator LoginViewModel

Slide 129

Slide 129 text

1. Idling Resources 2. Waiting for things 3. Replace components 3. Replace components LoginViewModel

Slide 130

Slide 130 text

1. Idling Resources 2. Waiting for things 3. Replace components 3. Replace components LoginViewModel Fake Email validator

Slide 131

Slide 131 text

1. Idling Resources 2. Waiting for things 3. Replace components d.android.com/training/testing

Slide 132

Slide 132 text

Replacing production modules with fakes

Slide 133

Slide 133 text

class MyTestSuite { @get:Rule var activityScenarioRule = activityScenarioRule() @Test fun testEvent() { val scenario = activityScenarioRule.scenario ... } }

Slide 134

Slide 134 text

No content

Slide 135

Slide 135 text

Hilt

Slide 136

Slide 136 text

@HiltAndroidTest class LoginScreenTest { @get:Rule var hiltRule = HiltAndroidRule(this) // UI tests here. }

Slide 137

Slide 137 text

@Module @InstallIn(SingletonComponent::class) abstract class AuthenticationModule { @Singleton @Binds abstract fun bindAuthService( AuthServiceImpl: AuthServiceImpl ): AuthService }

Slide 138

Slide 138 text

abstract class FakeAuthenticationModule { @Singleton @Binds abstract fun bindAuthService( fakeAuthService: FakeAuthService ): AuthService }

Slide 139

Slide 139 text

@Module @TestInstallIn( components = [SingletonComponent::class], replaces = [AuthenticationModule::class] ) abstract class FakeAuthenticationModule { @Singleton @Binds abstract fun bindAuthService( fakeAuthService: FakeAuthService ): AuthService }

Slide 140

Slide 140 text

d.android.com/training/dependency-injection/hilt-testing

Slide 141

Slide 141 text

Next steps

Slide 142

Slide 142 text

1. Implement app architecture 2. Configure CI service 3. Write unit tests 4. Screenshot tests for Previews 5. Write smoke tests 6. Write tests for bugs 7. Test everything else 1. Implement app architecture d.android.com/topic/architecture

Slide 143

Slide 143 text

1. Implement app architecture 2. Configure CI service 3. Write unit tests 4. Screenshot tests for Previews 5. Write smoke tests 6. Write tests for bugs 7. Test everything else 2. Configure CI service Build Local tests Instrumented tests CI Service

Slide 144

Slide 144 text

1. Implement app architecture 2. Configure CI service 3. Write unit tests 4. Screenshot tests for Previews 5. Write smoke tests 6. Write tests for bugs 7. Test everything else 3. Write unit tests UI Layer UI elements State holders Data Layer Repositories Data sources Domain Layer Use cases

Slide 145

Slide 145 text

1. Implement app architecture 2. Configure CI service 3. Write unit tests 4. Screenshot tests for Previews 5. Write smoke tests 6. Write tests for bugs 7. Test everything else 4. Screenshot tests for Previews Golden New Difference

Slide 146

Slide 146 text

1. Implement app architecture 2. Configure CI service 3. Write unit tests 4. Screenshot tests for Previews 5. Write smoke tests 6. Write tests for bugs 7. Test everything else 5. Write smoke tests Large • Render app • Verify properties • Interact • Verify properties • Interact • Verify properties • Interact • Verify properties

Slide 147

Slide 147 text

1. Implement app architecture 2. Configure CI service 3. Write unit tests 4. Screenshot tests for Previews 5. Write smoke tests 6. Write tests for bugs 7. Test everything else 6. Write tests for bugs Blimey! I can't log into the app. Rubbish. D. Turner July 6th, 2022

Slide 148

Slide 148 text

1. Implement app architecture 2. Configure CI service 3. Write unit tests 4. Screenshot tests for Previews 5. Write smoke tests 6. Write tests for bugs 7. Test everything else 6. Write tests for bugs class EmailValidatorTest { }

Slide 149

Slide 149 text

1. Implement app architecture 2. Configure CI service 3. Write unit tests 4. Screenshot tests for Previews 5. Write smoke tests 6. Write tests for bugs 7. Test everything else 6. Write tests for bugs class EmailValidatorTest { emailAddress_withMultiplePartsDomain_isValid() { assertTrue( EmailValidator().isValid(“[email protected]") ) } }

Slide 150

Slide 150 text

1. Implement app architecture 2. Configure CI service 3. Write unit tests 4. Screenshot tests for Previews 5. Write smoke tests 6. Write tests for bugs 7. Test everything else 6. Write tests for bugs class EmailValidatorTest { emailAddress_withMultiplePartsDomain_isValid() { assertTrue( EmailValidator().isValid(“[email protected]") ) } }

Slide 151

Slide 151 text

1. Implement app architecture 2. Configure CI service 3. Write unit tests 4. Screenshot tests for Previews 5. Write smoke tests 6. Write tests for bugs 7. Test everything else 7. Test everything else Step 1: Draw circles and lines Step 2: Finish drawing

Slide 152

Slide 152 text

1. Implement app architecture 2. Configure CI service 3. Write unit tests 4. Screenshot tests for Previews 5. Write smoke tests 6. Write tests for bugs 7. Test everything else 7. Test everything else Element Class Method Line home 80% 78% 69% login 95% 92% 88% profile 90% 83% 79% ui 75% 75% 70% Test coverage

Slide 153

Slide 153 text

Learning about testing

Slide 154

Slide 154 text

d.android.com/training/testing Testing documentation Testing tools documentation Testing Compose Compose testing cheat sheet Espresso cheat sheet Compose testing codelab Other testing code labs Testing documentation

Slide 155

Slide 155 text

d.android.com/studio/test Testing documentation Testing tools documentation Testing Compose Compose testing cheat sheet Espresso cheat sheet Compose testing codelab Other testing code labs Testing tools documentation

Slide 156

Slide 156 text

d.android.com/jetpack/compose/testing Testing documentation Testing tools documentation Testing your Compose layout Compose testing cheat sheet Espresso cheat sheet Compose testing codelab Other testing code labs Testing Compose

Slide 157

Slide 157 text

Testing documentation Testing tools documentation Testing your Compose layout Compose testing cheat sheet Espresso cheat sheet Compose testing codelab Other testing code labs Compose testing cheat sheet

Slide 158

Slide 158 text

onView(ViewMatcher) .perform(ViewAction) .check(ViewAssertion); isDisplayed() isCompletelyDisplayed() isEnabled() hasFocus() isClickable() isChecked() isNotChecked() withEffectiveVisibility(...) isSelected() UI PROPERTIES allOf(Matchers) anyOf(Matchers) is(...) not(...) endsWith(String) startsWith(String) OBJECT MATCHER withId(...) withText(...) withTagKey(...) withTagValue(...) hasContentDescription(...) withContentDescription(...) withHint(...) withSpinnerText(...) hasLinks() hasEllipsizedText() hasMultilineTest() View Matchers USER PROPERTIES withParent(Matcher) withChild(Matcher) hasDescendant(Matcher) isDescendantOfA(Matcher) hasSibling(Matcher) isRoot() HIERARCHY supportsInputMethods(...) hasIMEAction(...) INPUT isAssignableFrom(...) withClassName(...) CLASS isFocusable() isTouchable() isDialog() withDecorView() isPlatformPopup() ROOT MATCHERS Preference matchers Cursor matchers Layout matchers SEE ALSO click() doubleClick() longClick() pressBack() pressIMEActionButton() pressKey([int/EspressoKey]) pressMenuKey() closeSoftKeyboard() openLink() View Actions CLICK/PRESS scrollTo() swipeLeft() swipeRight() swipeUp() swipeDown() GESTURES clearText() typeText(String) typeTextIntoFocusedView(String) replaceText(String) TEXT matches(Matcher) doesNotExist() View Assertions POSITION ASSERTIONS onData(ObjectMatcher) .DataOptions .perform(ViewAction) .check(ViewAssertion); inAdapterView(Matcher) atPosition(Integer) onChildView(Matcher) Data Options Testing documentation Testing tools documentation Testing your Compose layout Compose testing cheat sheet Espresso cheat sheet Compose testing codelab Other testing code labs Espresso cheat sheet

Slide 159

Slide 159 text

Testing documentation Testing tools documentation Testing your Compose layout Compose testing cheat sheet Espresso cheat sheet Compose testing codelab Other testing code labs Compose testing codelab

Slide 160

Slide 160 text

Testing documentation Testing tools documentation Testing your Compose layout Compose testing cheat sheet Espresso cheat sheet Compose testing codelab Other testing code labs Other testing codelabs

Slide 161

Slide 161 text

Now what?

Slide 162

Slide 162 text

when(developer) { Developer.BEGINNER -> developer.test() Developer.SKEPTIC -> developer.tryAgain() Developer.EXPERT -> developer.becomeTestingHero() }

Slide 163

Slide 163 text

Happy Testing!