Continuation of the talk "Solving the problems of Espresso Android autotests in the real world". In this talk, we were solving infrastructure problems, for now - internal ones.
In this talk, Alexey will show the examples of typical Android autotest mistakes which will lead to the need for large arrangements. Speaker will show how to avoid these mistakes by applying various solutions taking into account the features of mobile infrastructure. It will be shown on the example of a demo application on GitHub. Many of the approaches will be standard, some of them are more suitable for Android automation.
14. TEST CLASS SKELETON
class SomeTestClass {
companion object {
@BeforeClass @JvmStatic
fun executeBeforeAllTests(){}
}
...
}
14
15. TEST CLASS SKELETON
class SomeTestClass {
companion object {
@BeforeClass @JvmStatic
fun executeBeforeAllTests(){}
}
@Before
fun executeBeforeEachTest(){}
...
}
15
16. TEST CLASS SKELETON
class SomeTestClass {
companion object {
@BeforeClass @JvmStatic
fun executeBeforeAllTests(){}
}
@Before
fun executeBeforeEachTest(){}
@Test
fun assertSmthTest(){}
}
16
17. TEST CLASS SKELETON
class SomeTestClass {
companion object {
@BeforeClass @JvmStatic
fun executeBeforeAllTests(){}
}
@get:Rule
val activityRule = ActivityTestRule(MainActivity::class.java)
@Before
fun executeBeforeEachTest(){}
@Test
fun assertSmthTest(){}
}
17
20. TEST DATA IS USED EVERYWHERE
object TestData {
val chatId = "..."
val documentId = "..."
}
20
21. TEST DATA IS USED EVERYWHERE
object TestData {
val chatId = "..."
val documentId = "..."
}
// Somewhere in user steps
fun sendDocInChat(user: User){
addDocToUser(user, TestData.documentId)
sendDoc(TestData.chatId,
TestData.documentId)
}
21
22. TEST DATA IS USED EVERYWHERE
object TestData {
val chatId = "..."
val documentId = "..."
}
// Somewhere in user steps
fun sendDocInChat(user: User){
addDocToUser(user, TestData.documentId)
sendDoc(TestData.chatId,
TestData.documentId)
}
// Somewhere in test class
@Test fun someTest(){
sendDocInChat(user)
}
22
23. TEST DATA IS USED EVERYWHERE
WHAT’S A PROBLEM
23
24. TEST DATA IS USED EVERYWHERE
WHAT’S A PROBLEM
▸ Lost a control on test data
24
25. TEST DATA IS USED EVERYWHERE
WHAT’S A PROBLEM
▸ Lost a control on test data
▸ Unexpected test application condition
25
26. TEST DATA IS USED EVERYWHERE
WHAT’S A PROBLEM
▸ Lost a control on test data
▸ Unexpected test application condition
▸ The same data can be used by several tests at the same time
26
30. SOLUTION
▸ Restrict the scope of test data definition to test class / test suite
TEST DATA USAGE 30
31. TEST DATA IS USED EVERYWHERE
object TestData {
val chatId = "..."
val documentId = "..."
}
// Somewhere in user steps
fun sendDocInChat(user: User){
addDocToUser(user, TestData.documentId)
sendDoc(TestData.chatId,
TestData.documentId)
}
31
32. RESTRICT THE SCOPE OF TEST DATA DEFINITION TO TEST CLASS / TEST SUITE
object TestData {
val chatId = "..."
val documentId = "..."
}
// Somewhere in user steps
fun sendDocInChat(user: User, docId: String, chatId: String){
addDocToUser(user, docId)
sendDoc(chatId, docId)
}
32
33. object TestData {
val chatId = "..."
val documentId = "..."
}
// Somewhere in user steps
fun sendDocInChat(user: User, docId: String, chatId: String){
addDocToUser(user, docId)
sendDoc(chatId, docId)
}
33RESTRICT THE SCOPE OF TEST DATA DEFINITION TO TEST CLASS / TEST SUITE
34. object TestData {
val chatId = "..."
val documentId = "..."
}
// Somewhere in user steps
fun sendDocInChat(user: User, docId: String, chatId: String){
addDocToUser(user, docId)
sendDoc(chatId, docId)
}
// Somewhere in test class
@Test fun someTest(){
sendDocInChat(user, documentId, chatId)
}
34RESTRICT THE SCOPE OF TEST DATA DEFINITION TO TEST CLASS / TEST SUITE
35. SOLUTION
▸ Restrict the scope of test data definition to test class / test suite
TEST DATA USAGE 35
36. SOLUTION
▸ Restrict the scope of test data definition to test class / test suite
▸ Store read-only data as project constants
TEST DATA USAGE 36
37. STORE READ-ONLY DATA AS PROJECT CONSTANTS
object TestData {
// editable value, should be calculated somehow
val chatId = TestDataManager.createChat()
val documentId = "..."
}
// Somewhere in test class
@Test fun someTest(){
sendDocInChat(user, documentId, chatId)
}
37
38. STORE READ-ONLY DATA AS PROJECT CONSTANTS
object TestData {
// editable value, should be calculated somehow
val chatId = TestDataManager.createChat()
val documentId = TestData.documentId // read-only value
}
// Somewhere in test class
@Test fun someTest(){
sendDocInChat(user, documentId, chatId)
}
38
40. MISTAKE
TEST DATA IS DEFINED AFTER ACTIVITY LAUNCH
@get:Rule
val activityRule =
ActivityTestRule(MainActivity::class.java)
@Before
fun setUp(){
Api.addChatMessage(contact.id, "text")
}
40
41. TEST CLASS METHODS ORDER
@get:Rule
val activityRule =
ActivityTestRule(MainActivity::class.java)
@Before
fun setUp(){
Api.addChatMessage(contact.id, "text")
}
REASON: @BEFORE EXECUTES AFTER ACTIVITY LAUNCHED
41
42. TEST CLASS METHODS ORDER
@get:Rule
val activityRule =
ActivityTestRule(MainActivity::class.java)
@Before
fun setUp(){
Api.addChatMessage(contact.id, "text")
}
ActivityLaunched
REASON: @BEFORE EXECUTES AFTER ACTIVITY LAUNCHED
42
43. TEST CLASS METHODS ORDER
@get:Rule
val activityRule =
ActivityTestRule(MainActivity::class.java)
@Before
fun setUp(){
Api.addChatMessage(contact.id, "text")
}
REASON: @BEFORE EXECUTES AFTER ACTIVITY LAUNCHED
@Before
43
ActivityLaunched
44. TEST CLASS METHODS ORDER
@get:Rule
val activityRule =
ActivityTestRule(MainActivity::class.java)
@Before
fun setUp(){
Api.addChatMessage(contact.id, "text")
}
REASON: @BEFORE EXECUTES AFTER ACTIVITY LAUNCHED
@Test
44
@Before
ActivityLaunched
45. TEST CLASS METHODS ORDER
@get:Rule
val activityRule =
ActivityTestRule(MainActivity::class.java)
@Before
fun setUp(){
Api.addChatMessage(contact.id, "text")
}
REASON: @BEFORE EXECUTES AFTER ACTIVITY LAUNCHED
45
48. WHEN TEST DATA SHOULD BE DEFINED
LAUNCH
ACTIVITY
DEFINE
TEST DATA
48
49. WHEN TEST DATA SHOULD BE DEFINED
LAUNCH
ACTIVITY
DEFINE
TEST DATA @TEST 1
49
50. WHEN TEST DATA SHOULD BE DEFINED
LAUNCH
ACTIVITY
DEFINE
TEST DATA @TEST 1
@TEST 2
50
51. TEST CLASS METHODS ORDER
@BeforeClass
@get:Rule
val activityRule =
ActivityTestRule(MainActivity::class.java)
companion object {
@BeforeClass @JvmStatic
fun setUpTestData(){
Api.addChatMessage(contact.id, "text")
}
}
SOLUTION #1: SET UP DATA IN @BEFORECLASS
51
52. TEST CLASS METHODS ORDER
@BeforeClass
@get:Rule
val activityRule =
ActivityTestRule(MainActivity::class.java)
companion object {
@BeforeClass @JvmStatic
fun setUpTestData(){
Api.addChatMessage(contact.id, "text")
}
}
SOLUTION #1: SET UP DATA IN @BEFORECLASS
52
53. TEST CLASS METHODS ORDER
@BeforeClass
@get:Rule
val activityRule =
ActivityTestRule(MainActivity::class.java)
companion object {
@BeforeClass @JvmStatic
fun setUpTestData(){
Api.addChatMessage(contact.id, "text")
}
}
SOLUTION #1: SET UP DATA IN @BEFORECLASS
ActivityLaunched
@Test
53
54. TEST CLASS METHODS ORDER
@BeforeClass
@get:Rule
val activityRule =
ActivityTestRule(MainActivity::class.java)
companion object {
@BeforeClass @JvmStatic
fun setUpTestData(){
Api.addChatMessage(contact.id, "text")
}
}
SOLUTION #1: SET UP DATA IN @BEFORECLASS
ActivityLaunched
@Test
54
55. TEST CLASS METHODS ORDER
@get:Rule
val activityRule =
ActivityTestRule(MainActivity::class.java,
initialTouchMode = false,
launchActivity = false)
@Before
fun setUp(){
Api.addChatMessage(contact.id, "text")
activityRule.launchActivity(Intent())
}
SOLUTION #2: LAUNCH ACTIVITY = FALSE
55
56. TEST CLASS METHODS ORDER
@get:Rule
val activityRule =
ActivityTestRule(MainActivity::class.java,
initialTouchMode = false,
launchActivity = false)
@Before
fun setUp(){
Api.addChatMessage(contact.id, "text")
activityRule.launchActivity(Intent())
}
SOLUTION #2: LAUNCH ACTIVITY = FALSE
56
57. TEST CLASS METHODS ORDER
@BeforeClass
@get:Rule
val activityRule =
ActivityTestRule(MainActivity::class.java,
initialTouchMode = false,
launchActivity = false)
@Before
fun setUp(){
Api.addChatMessage(contact.id, "text")
activityRule.launchActivity(Intent())
}
SOLUTION #2: LAUNCH ACTIVITY = FALSE
57
58. TEST CLASS METHODS ORDER
@BeforeClass
@Before
@get:Rule
val activityRule =
ActivityTestRule(MainActivity::class.java,
initialTouchMode = false,
launchActivity = false)
@Before
fun setUp(){
Api.addChatMessage(contact.id, "text")
activityRule.launchActivity(Intent())
}
SOLUTION #2: LAUNCH ACTIVITY = FALSE
58
59. TEST CLASS METHODS ORDER
@BeforeClass
@Before
@get:Rule
val activityRule =
ActivityTestRule(MainActivity::class.java,
initialTouchMode = false,
launchActivity = false)
@Before
fun setUp(){
Api.addChatMessage(contact.id, "text")
activityRule.launchActivity(Intent())
}
SOLUTION #2: LAUNCH ACTIVITY = FALSE
59
60. TEST CLASS METHODS ORDER
@BeforeClass
@Before
ActivityLaunched
@get:Rule
val activityRule =
ActivityTestRule(MainActivity::class.java,
initialTouchMode = false,
launchActivity = false)
@Before
fun setUp(){
Api.addChatMessage(contact.id, "text")
activityRule.launchActivity(Intent())
}
SOLUTION #2: LAUNCH ACTIVITY = FALSE
60
61. TEST CLASS METHODS ORDER
@BeforeClass
@Before
ActivityLaunched
@Test
@get:Rule
val activityRule =
ActivityTestRule(MainActivity::class.java,
initialTouchMode = false,
launchActivity = false)
@Before
fun setUp(){
Api.addChatMessage(contact.id, "text")
activityRule.launchActivity(Intent())
}
SOLUTION #2: LAUNCH ACTIVITY = FALSE
61
62. TEST CLASS METHODS ORDER
SOLUTION #3: CUSTOM SETUP RULE
class SetUpTearDownRule : TestWatcher() {
val setUps = mutableListOf<Condition>()
val tearDowns = mutableListOf<Condition>()
}
62
63. TEST CLASS METHODS ORDER
SOLUTION #3: CUSTOM SETUP RULE
class SetUpTearDownRule : TestWatcher() {
val setUps = mutableListOf<Condition>()
val tearDowns = mutableListOf<Condition>()
}
63
64. TEST CLASS METHODS ORDER
SOLUTION #3: CUSTOM SETUP RULE
class SetUpTearDownRule : TestWatcher() {
val setUps = mutableListOf<Condition>()
val tearDowns = mutableListOf<Condition>()
}
64
65. TEST CLASS METHODS ORDER
class SetUpTearDownRule : TestWatcher() {
private val setUps = mutableListOf<Condition>()
inner class Condition(
val counter: Int,
val key: String,
val actions: () -> Unit
)
}
SOLUTION #3: CUSTOM SETUP RULE
65
66. TEST CLASS METHODS ORDER
class SomeTestClass {
@get:Rule
val setupRule = SetUpTearDownRule()
.addSetUp {
Api.addChatMessage(contact.id, "text")
}
...
}
SOLUTION #3: CUSTOM SETUP RULE
66
67. TEST CLASS METHODS ORDER
class SomeTestClass {
@get:Rule
val setupRule = SetUpTearDownRule()
.addSetUp {
Api.addChatMessage(contact.id, "text")
}
...
}
SOLUTION #3: CUSTOM SETUP RULE
67
68. TEST CLASS METHODS ORDER
class SomeTestClass {
@get:Rule
val setupRule = SetUpTearDownRule()
.addSetUp {
Api.addChatMessage(contact.id, "text")
}
@get:Rule
val activityRule =
ActivityTestRule(MainActivity::class.java)
...
}
@BeforeClassSOLUTION #3: CUSTOM SETUP RULE
68
69. TEST CLASS METHODS ORDER
class SomeTestClass {
@get:Rule
val setupRule = SetUpTearDownRule()
.addSetUp {
Api.addChatMessage(contact.id, "text")
}
@get:Rule
val activityRule =
ActivityTestRule(MainActivity::class.java)
...
}
@BeforeClass
SetUps.actions
SOLUTION #3: CUSTOM SETUP RULE
69
70. TEST CLASS METHODS ORDER
class SomeTestClass {
@get:Rule
val setupRule = SetUpTearDownRule()
.addSetUp {
Api.addChatMessage(contact.id, "text")
}
@get:Rule
val activityRule =
ActivityTestRule(MainActivity::class.java)
...
}
@BeforeClass
SetUps.actions
ActivityLaunched
SOLUTION #3: CUSTOM SETUP RULE
70
71. TEST CLASS METHODS ORDER
class SomeTestClass {
@get:Rule
val setupRule = SetUpTearDownRule()
.addSetUp {
Api.addChatMessage(contact.id, "text")
}
@get:Rule
val activityRule =
ActivityTestRule(MainActivity::class.java)
...
}
@BeforeClass
SetUps.actions
ActivityLaunched
@Before
SOLUTION #3: CUSTOM SETUP RULE
71
72. TEST CLASS METHODS ORDER
class SomeTestClass {
@get:Rule
val setupRule = SetUpTearDownRule()
.addSetUp {
Api.addChatMessage(contact.id, "text")
}
@get:Rule
val activityRule =
ActivityTestRule(MainActivity::class.java)
...
}
@BeforeClass
SetUps.actions
ActivityLaunched
@Before
@Test
SOLUTION #3: CUSTOM SETUP RULE
72
73. TEST CLASS METHODS ORDER
class SomeTestClass {
@get:Rule
val setupRule = SetUpTearDownRule()
.addSetUp {
Api.addChatMessage(contact.id, "text")
}
@get:Rule
val activityRule =
ActivityTestRule(MainActivity::class.java)
...
}
SOLUTION #3: CUSTOM SETUP RULE
73
75. TEST CLASS METHODS ORDER
class SomeTestClass {
@get:Rule
val setupRule = SetUpTearDownRule()
.addSetUp {
Api.addChatMessage(contact.id, "text")
}
@get:Rule
val activityRule =
ActivityTestRule(MainActivity::class.java)
...
}
SOLUTION #3 DOESN’T WORK ALWAYS! WHY?
75
76. TEST CLASS METHODS ORDER
class SomeTestClass {
@get:Rule
val setupRule = SetUpTearDownRule()
.addSetUp {
Api.addChatMessage(contact.id, "text")
}
@get:Rule
val activityRule =
ActivityTestRule(MainActivity::class.java)
...
}
SOLUTION #3 DOESN’T WORK ALWAYS! WHY?
76
77. TEST CLASS METHODS ORDER
@get:Rule
val setupRule = SetUpTearDownRule().addSetUp{...}
@get:Rule
val activityRule = ActivityTestRule(MainActivity::class.java)
@get:Rule
val ruleChain = RuleChain.outerRule(setupRule)
.around(activityRule)
ORDER RULES WITH RULECHAIN
77
78. TEST CLASS METHODS ORDER
@get:Rule
val setupRule = SetUpTearDownRule().addSetUp{...}
@get:Rule
val activityRule = ActivityTestRule(MainActivity::class.java)
@get:Rule
val ruleChain = RuleChain.outerRule(setupRule)
.around(activityRule)
ORDER RULES WITH RULECHAIN
78
79. TEST CLASS METHODS ORDER
val setupRule = SetUpTearDownRule().addSetUp{...}
val activityRule = ActivityTestRule(MainActivity::class.java)
@get:Rule
val ruleChain = RuleChain.outerRule(setupRule)
.around(activityRule)
ORDER RULES WITH RULECHAIN
79
80. 80
MARTY, ARE YOU REALLY
TELLING ME YOUR PLAN
IS BASED ON
“BACK TO THE FUTURE”?
81. 81
MARTY, ARE YOU REALLY
TELLING ME YOUR PLAN
IS BASED ON
“RULE CHAIN” IN 2020?
82. TEST CLASS METHODS ORDER
val setupRule = SetUpTearDownRule().addSetUp{...}
val activityRule = ActivityTestRule(MainActivity::class.java)
@get:Rule
val ruleChain = RuleChain.outerRule(setupRule)
.around(activityRule)
ORDER RULES WITH RULECHAIN
82
83. TEST CLASS METHODS ORDER
val setupRule = SetUpTearDownRule().addSetUp{...}
val activityRule = ActivityTestRule(MainActivity::class.java)
@get:Rule
val ruleChain = RuleChain.outerRule(setupRule)
.around(activityRule)
@get:Rule
val ruleSequence = RuleSequence(setupRule, activityRule)
REPLACE RULE CHAIN WITH RULE SEQUENCE
83
84. TEST CLASS METHODS ORDER
ADVANCES OF RULE SEQUENCE
84
▸ Friendly interface
85. TEST CLASS METHODS ORDER
ADVANCES OF RULE SEQUENCE
85
▸ Friendly interface
▸ Better work with test classes inheritance
86. TEST CLASS METHODS ORDER
ADVANCES OF RULE SEQUENCE
86
▸ Friendly interface
▸ Better work with test classes inheritance
▸ Three lists of prioritised rules (add, addFirst, addLast)
87. SOLUTIONS
TEST DATA IS DEFINED AFTER ACTIVITY LAUNCH
@BeforeClass
SetUps.actions
@Before
ActivityLaunched
@Test
87
88. SOLUTIONS
TEST DATA IS DEFINED AFTER ACTIVITY LAUNCH
@BeforeClass
SetUps.actions
@Before
ActivityLaunched
@Test
88
89. SOLUTIONS
▸ Set up test data in @BeforeClass
TEST DATA IS DEFINED AFTER ACTIVITY LAUNCH
SetUps.actions
@Before
@BeforeClass
89
ActivityLaunched
@Test
90. SOLUTIONS
▸ Set up test data in @BeforeClass
▸ LaunchActivity = false. Set up in @Before
TEST DATA IS DEFINED AFTER ACTIVITY LAUNCH
@BeforeClass
SetUps.actions
@Before@Before
90
ActivityLaunched
@Test
91. SOLUTIONS
▸ Set up test data in @BeforeClass
▸ LaunchActivity = false. Set up in @Before
▸ Custom SetUpRule + RuleSequence
TEST DATA IS DEFINED AFTER ACTIVITY LAUNCH
@BeforeClass
SetUps.actions
@Before
91
ActivityLaunched
@Test
92. SOLUTIONS
▸ Set up test data in @BeforeClass
▸ LaunchActivity = false. Set up in @Before
▸ Custom SetUpRule + RuleSequence (best way)
TEST DATA IS DEFINED AFTER ACTIVITY LAUNCH
@BeforeClass
SetUps.actions
@Before
92
ActivityLaunched
@Test
94. TEST DATA IS DEFINED IN @BEFORECLASS FOR ALL @TEST
class SomeTestClass {
companion object {
@BeforeClass @JvmStatic
fun setUpTestData(){
Api.clearChatMessages(contact.id)
Api.addChatMessage(contact.id, longMessage)
Api.addChatMessage(contact.id, simpleMessage)
Api.addChatMessage(contact.id, specialCharsMessage)
}
}
}
94
95. TEST DATA IS DEFINED IN @BEFORE METHOD FOR ALL @TEST
class SomeTestClass {
@Before
fun setUpTestData(){
Api.clearChatMessages(contact.id)
Api.addChatMessage(contact.id, longMessage)
Api.addChatMessage(contact.id, simpleMessage)
Api.addChatMessage(contact.id, specialCharsMessage)
}
}
95
96. SIMPLE TEXT ASSERTION BECOMES HARDER
@Test
fun assertSimpleMessage() {
ChatPage(contact)
.assertMessageDisplayed(simpleMessage.text)
}
96
97. SIMPLE TEXT ASSERTION BECOMES HARDER
@Test
fun assertSimpleMessage() {
ChatPage(contact)
.assertMessageDisplayed(simpleMessage.text)
}
97
114. TEST DATA IS RELATED WITH BUILD.SERIAL
class SomeTestClass {
...
val user = TestData.getUser(Build.Serial)
}
114
115. TEST DATA IS RELATED WITH BUILD.SERIAL
Tests
runner
server
115
116. TEST DATA IS RELATED WITH BUILD.SERIAL
USER 1
Tests
runner
server
116
117. TEST DATA IS RELATED WITH BUILD.SERIAL
USER 1 USER 2 USER 3 USER 4 USER 5
Tests
runner
server
117
118. TEST DATA IS RELATED WITH BUILD.SERIAL
USER 1 USER 2 USER 3 USER 4 USER 5
Tests
runner
server
118
119. TEST DATA IS RELATED WITH BUILD.SERIAL
USER 1 USER 2 USER 3 USER 4 USER 5 USER 6
Tests
runner
server
119
120. TEST DATA IS RELATED WITH BUILD.SERIAL
https://openstf.io/
120
121. TEST DATA IS RELATED WITH BUILD.SERIAL
IF WE WANNA ADD OR CHANGE A DEVICE
121
122. TEST DATA IS RELATED WITH BUILD.SERIAL
IF WE WANNA ADD OR CHANGE A DEVICE
▸ Support changing in test code
122
123. TEST DATA IS RELATED WITH BUILD.SERIAL
IF WE WANNA ADD OR CHANGE A DEVICE
▸ Support changing in test code
▸ Configure test user to default test conditions
123
124. TEST DATA IS RELATED WITH BUILD.SERIAL
Tests
runner
server
124
125. TEST DATA IS RELATED WITH BUILD.SERIAL
Tests
runner
server
Test
data
server
125
126. TEST DATA IS RELATED WITH BUILD.SERIAL
Tests
runner
server
Test
data
server
126
127. TEST DATA IS RELATED WITH BUILD.SERIAL
ANY
USER
Tests
runner
server
Test
data
server
ANY
USER
ANY
USER
ANY
USER
ANY
USER
ANY
USER
127
128. WHAT TEST DATA SERVER PROVIDES TO US
Test
data
server
128
129. ▸ Pool of test users and any other required objects
WHAT TEST DATA SERVER PROVIDES TO US
Test
data
server
129
130. ▸ Pool of test users and any other required objects
▸ Data management via rest api
WHAT TEST DATA SERVER PROVIDES TO US
Test
data
server
130
131. ▸ Pool of test users and any other required objects
▸ Data management via rest api
▸ Guarantee of test data uniqueness for separate test
WHAT TEST DATA SERVER PROVIDES TO US
Test
data
server
131
132. ▸ Kotlin
▸ Spring Boot (https://spring.io/projects/spring-boot)
▸ JetBrains Exposed (https://github.com/JetBrains/Exposed)
WHAT TEST DATA SERVER FORMULA
Test
data
server
132
134. SOLUTION: TEST DATA SERVER
▸ No need to support devices
TEST DATA IS RELATED WITH BUILD.SERIAL 134
135. SOLUTION: TEST DATA SERVER
▸ No need to support devices
▸ Single point to control all test data
TEST DATA IS RELATED WITH BUILD.SERIAL 135
136. SOLUTION: TEST DATA SERVER
▸ No need to support devices
▸ Single point to control all test data
▸ Multiple test runs at the same time (CI tasks)
TEST DATA IS RELATED WITH BUILD.SERIAL 136
137. SOLUTION: TEST DATA SERVER
▸ No need to support devices
▸ Single point to control all test data
▸ Multiple test runs at the same time (CI tasks)
▸ It could be used by other test frameworks (iOS, Web, api tests)
TEST DATA IS RELATED WITH BUILD.SERIAL 137
138. TEST DATA USAGE
▸ Test data is used everywhere:
-> Strict data definition to test suite or test case
-> Store read-only data as constants
138
139. TEST DATA USAGE
▸ Test data is used everywhere:
-> Strict data definition to test suite or test case
-> Store read-only data as constants
▸ Test data is defined after activity launch
-> Use custom SetUpTearDown rule + RuleSequence
139
140. TEST DATA USAGE
▸ Test data is used everywhere:
-> Strict data definition to test suite or test case
-> Store read-only data as constants
▸ Test data is defined after activity launch
-> Use custom SetUpTearDown rule + RuleSequence
▸ Test data is defined in @BeforeClass or @Before method for all @Test
methods
-> Use SetUpTearDown rule + SetUp and TearDown annotations
140
141. TEST DATA USAGE
▸ Test data is used everywhere:
-> Strict data definition to test suite or test case
-> Store read-only data as constants
▸ Test data is defined after activity launch
-> Use custom SetUpTearDown rule + RuleSequence
▸ Test data is defined in @BeforeClass or @Before method for all @Test
methods
-> Use SetUpTearDown rule + SetUp and TearDown annotations
▸ Test user is related with device Build.Serial
-> Use TestData server
141
150. PREVIOUSLY ON HEISENBUG
COOL TEST
@Test
fun goodTest(){
FriendsListPage().openChat("Ross Geller")
ChatPage().clearHistory()
.sendMessage("test message")
}
150
151. IF I WANNA SEND ANOTHER MESSAGE
IS IT A GOOD IDEA?
@Test
fun sendMessageWithLink(){
FriendsListPage().openChat("Ross Geller”)
ChatPage().clearHistory()
.sendMessage("message with link vk.com")
}
151
152. IF I WANNA SEND ANOTHER MESSAGE
ACTUALLY IT ISN’T A GOOD IDEA!
@Test
fun sendMessageWithLink(){
FriendsListPage().openChat("Ross Geller”)
ChatPage().clearHistory()
.sendMessage("message with link vk.com")
}
152
158. LONG TEST SCENARIO
SOLUTION: SHORT TEST
@Test
fun sendMessageWithLink(){
FriendsListPage().openChat("Ross Geller”)
ChatPage().clearHistory()
.sendMessage("message with link vk.com")
}
158
159. LONG TEST SCENARIO
SOLUTION: SHORT TEST
@Test
fun sendMessageWithLink(){
FriendsListPage().openChat("Ross Geller”)
ChatPage().clearHistory()
ChatPage().sendMessage("message with link vk.com")
}
159
160. LONG TEST SCENARIO
SOLUTION: SHORT TEST
class ChatTest {
@Test
fun sendMessageWithLink() {
ChatPage().sendMessage("message with link vk.com")
}
}
160
161. SHORT TEST: LAUNCH TARGET ACTIVITY
class ChatTest {
@get:Rule
val activityRule = ActivityTestRule(MainActivity::class.java, false, false)
}
161
162. SHORT TEST: LAUNCH TARGET ACTIVITY
class ChatTest {
@get:Rule
val activityRule = ActivityTestRule(MainActivity::class.java, false, false)
}
162
163. class ChatTest {
@get:Rule
val activityRule = ActivityTestRule(ChatActivity::class.java, false, false)
}
SHORT TEST: LAUNCH TARGET ACTIVITY 163
164. SHORT TEST: LAUNCH TARGET ACTIVITY
class ChatTest {
@get:Rule
val activityRule = ActivityTestRule(ChatActivity::class.java, false, false)
@Before
fun prepareData() {
MessageRepository.clearChatMessages(contact.id)
val intent = Intent()
intent.putExtra("INTENT_CONTACT_ID_EXTRA_NAME", contact.id)
activityRule.launchActivity(intent)
}
}
164
165. SHORT TEST: LAUNCH TARGET ACTIVITY
class ChatTest {
@get:Rule
val activityRule = ActivityTestRule(ChatActivity::class.java, false, false)
@Before
fun prepareData() {
MessageRepository.clearChatMessages(contact.id)
val intent = Intent()
intent.putExtra("INTENT_CONTACT_ID_EXTRA_NAME", contact.id)
activityRule.launchActivity(intent)
}
}
165
166. SHORT TEST: LAUNCH TARGET ACTIVITY
class ChatTest {
@get:Rule
val activityRule = ActivityTestRule(ChatActivity::class.java, false, false)
@Before
fun prepareData() {
MessageRepository.clearChatMessages(contact.id)
val intent = Intent()
intent.putExtra("INTENT_CONTACT_ID_EXTRA_NAME", contact.id)
activityRule.launchActivity(intent)
}
}
166
167. SHORT TEST: LAUNCH TARGET ACTIVITY
class ChatTest {
@get:Rule
val activityRule = ActivityTestRule(ChatActivity::class.java, false, false)
@Before
fun prepareData() {
MessageRepository.clearChatMessages(contact.id)
val intent = Intent()
intent.putExtra("INTENT_CONTACT_ID_EXTRA_NAME", contact.id)
activityRule.launchActivity(intent)
}
}
167
168. SHORT TEST: LAUNCH TARGET ACTIVITY
class ChatTest {
@get:Rule
val activityRule = ActivityTestRule(ChatActivity::class.java, false, false)
@Before
fun prepareData() {
MessageRepository.clearChatMessages(contact.id)
val intent = Intent()
intent.putExtra("INTENT_CONTACT_ID_EXTRA_NAME", contact.id)
activityRule.launchActivity(intent)
}
}
168
169. SHORT TEST: LAUNCH TARGET ACTIVITY
class ChatTest {
@get:Rule
val activityRule = ActivityTestRule(ChatActivity::class.java, false, false)
@Before
fun prepareData() {
MessageRepository.clearChatMessages(contact.id)
val intent = Intent()
intent.putExtra("INTENT_CONTACT_ID_EXTRA_NAME", contact.id)
activityRule.launchActivity(intent)
}
@Test
fun sendMessageWithLink() {
ChatPage().sendMessage("message with link vk.com")
}
}
169
170. SHORT TEST: LAUNCH TARGET ACTIVITY
class ChatTest {
@get:Rule
val activityRule = ActivityTestRule(ChatActivity::class.java, false, false)
@Before
fun prepareData() {
MessageRepository.clearChatMessages(contact.id)
val intent = Intent()
intent.putExtra("INTENT_CONTACT_ID_EXTRA_NAME", contact.id)
activityRule.launchActivity(intent)
}
@Test
fun sendMessageWithLink() {...}
}
170
171. SHORT TEST: LAUNCH TARGET ACTIVITY
class ChatTest {
@get:Rule
val activityRule = ActivityTestRule(ChatActivity::class.java, false, false)
val setupRule = SetUpTearDownRule()
.addSetUp {
MessageRepository.clearChatMessages(contact.id)
val intent = Intent()
intent.putExtra("INTENT_CONTACT_ID_EXTRA_NAME", contact.id)
activityRule.launchActivity(intent)
}
@Test
fun sendMessageWithLink() {...}
}
171
172. SHORT TEST: LAUNCH TARGET ACTIVITY
class ChatTest {
@get:Rule
val activityRule = ActivityTestRule(ChatActivity::class.java, false, false)
val setupRule = SetUpTearDownRule()
.addSetUp {
MessageRepository.clearChatMessages(contact.id)
val intent = Intent()
intent.putExtra("INTENT_CONTACT_ID_EXTRA_NAME", contact.id)
activityRule.launchActivity(intent)
}
@Test
fun sendMessageWithLink() {...}
}
172
173. SHORT TEST: LAUNCH TARGET ACTIVITY
class ChatTest {
val activityRule = ActivityTestRule(ChatActivity::class.java, false, false)
val setupRule = SetUpTearDownRule()
.addSetUp {
MessageRepository.clearChatMessages(contact.id)
val intent = Intent()
intent.putExtra("INTENT_CONTACT_ID_EXTRA_NAME", contact.id)
activityRule.launchActivity(intent)
}
@get:Rule
val ruleSequence = RuleSequence(setupRule, activityRule)
@Test
fun sendMessageWithLink() {...}
}
173
178. LONG TEST SCENARIO
SOLUTION: SHORT TEST
class ChatTest {
@Test
fun sendMessageWithLink() {
ChatPage().sendMessage("message with link vk.com")
}
}
178
217. TEST FLOW
I HAD THE LONGEST TEST EVER BUT NOW …
▸ Long test scenario
217
218. TEST FLOW
I HAD THE LONGEST TEST EVER BUT NOW …
▸ Long test scenario
-> Use short tests by opening target activity
218
219. TEST FLOW
I HAD THE LONGEST TEST EVER BUT NOW …
▸ Long test scenario
-> Use short tests by opening target activity
▸ I navigate my tests to the Target Screen by UI actions
219
220. TEST FLOW
I HAD THE LONGEST TEST EVER BUT NOW …
▸ Long test scenario
-> Use short tests by opening target activity
▸ I navigate my tests to the Target Screen by UI actions
-> Use internal navigation mechanism
220
221. TEST FLOW
I HAD THE LONGEST TEST EVER BUT NOW …
▸ Long test scenario
-> Use short tests by opening target activity
▸ I navigate my tests to the Target Screen by UI actions
-> Use internal navigation mechanism
▸ Login by UI actions
221
222. TEST FLOW
I HAD THE LONGEST TEST EVER BUT NOW …
▸ Long test scenario
-> Use short tests by opening target activity
▸ I navigate my tests to the Target Screen by UI actions
-> Use internal navigation mechanism
▸ Login by UI actions
-> Do it by internal authorisation mechanism
222
223. TEST FLOW
▸ Long test scenario
-> Use short tests by opening target activity
▸ I navigate my tests to the Target Screen by UI actions
-> Use internal navigation mechanism
▸ Login by UI actions
-> Do it by internal authorisation mechanism
223
I HAD THE LONGEST TEST EVER BUT NOW I HAVE THE SHORTEST ONE
246. TYPES OF MISTAKES WE’VE DISCOVERED
BACK TO THE PAST
▸ Test data usage
246
247. TEST DATA USAGE
▸ Test data is used everywhere:
-> Strict data definition to test suite or test case
-> Store read-only data as constants
▸ Test data is defined after activity launch
-> Use custom SetUpTearDown rule
▸ Test data is defined in @BeforeClass or @Before method for all @Test
methods
-> Use SetUpTearDown rule + SetUp and TearDown annotations
▸ Test user is related with device Build.Serial
-> Use TestData server
247
248. TYPES OF MISTAKES WE’VE DISCOVERED
BACK TO THE PAST
▸ Test data usage
▸ Test flow
248
249. TEST FLOW
▸ Long test scenario
-> Use short tests by opening target activity
▸ I navigate my tests to the Target Screen by UI actions
-> Use internal navigation mechanism
▸ Login by UI actions
-> Do it by internal authorisation mechanism
249
I HAD THE LONGEST TEST EVER BUT NOW I HAVE THE SHORTEST ONE
250. TYPES OF MISTAKES WE’VE DISCOVERED
BACK TO THE PAST
▸ Test data usage
▸ Test flow
▸ Framework architecture
250
251. 251SOLUTION: USE COMMON PRACTICES AND ADAPT IT
TEST SUITES
TEST CLASSES
TEST DATA
TEST STEPSASSERTIONS
TEST ADAPTERS
APP UI
ELEMENTS
STEPS
PAGE OBJECT CLASS
252. TYPES OF MISTAKES WE’VE DISCOVERED
BACK TO THE PAST
▸ Test data usage
▸ Test flow
▸ Framework architecture
▸ Reporting
252