Share
Embed
Testing Robots (Kotlin Night, May 2016)
Libraries like Espresso allow UI tests to have stable interactions with your app, but without discipline these tests can become hard to manage and require frequent updating. This talk will cover how the so-called robot pattern allows you to create stable, readable, and maintainable tests with the aid of Kotlin's language features.
http://info.jetbrains.com/Kotlin-Night-2016.html
Video: https://youtu.be/7Y3qIIEyP5c
Jake Wharton PRO
May 17, 2016
Transcript
96 slides
-
$0 1 2 3 4 5 6 7 8 9
0 < . Recipient Send -
Send $42 to "[email protected]". Verify success. $0 1 2 3
4 5 6 7 8 9 0 < . Recipient Send -
• Enter "42" with the number pad. $0 1 2
3 4 5 6 7 8 9 0 < . Recipient Send Send $42 to "[email protected]". Verify success. -
Send $42 to "[email protected]". Verify success. • Enter "42" with
the number pad. • Tap on the input box marked "Recipient". $0 1 2 3 4 5 6 7 8 9 0 < . Recipient Send -
Send $42 to "[email protected]". Verify success. • Enter "42" with
the number pad. • Tap on the input box marked "Recipient". • Type "[email protected]". $0 1 2 3 4 5 6 7 8 9 0 < . Recipient Send -
Send $42 to "[email protected]". Verify success. • Enter "42" with
the number pad. • Tap on the input box marked "Recipient". • Type "[email protected]". • Press the button marked "Send". $0 1 2 3 4 5 6 7 8 9 0 < . Recipient Send -
Send $42 to "[email protected]". Verify success. • Enter "42" with
the number pad. • Tap on the input box marked "Recipient". • Type "[email protected]". • Press the button marked "Send". • Verify success screen is shown. $0 1 2 3 4 5 6 7 8 9 0 < . Recipient Send -
PaymentScreen pay = (PaymentScreen) obtainScreen(); $0 1 2 3 4
5 6 7 8 9 0 < . Recipient Send -
PaymentScreen pay = (PaymentScreen) obtainScreen(); pay.amountView.setValue(42_00); pay.recipientView.setText("[email protected]"); $0 1 2
3 4 5 6 7 8 9 0 < . Recipient Send -
PaymentScreen pay = (PaymentScreen) obtainScreen(); pay.amountView.setValue(42_00); pay.recipientView.setText("[email protected]"); pay.sendView.click(); Thread.sleep(1000);
assertThat(obtainScreen()) .isInstanceOf(SuccessScreen.class); $0 1 2 3 4 5 6 7 8 9 0 < . Recipient Send -
A Test A PaymentScreen pay = (PaymentScreen) obtainScreen(); pay.amountView.setValue(42_00); pay.recipientView.setText("[email protected]");
pay.sendView.click(); Thread.sleep(1000); assertThat(obtainScreen()) .isInstanceOf(SuccessScreen.class); Presenter View Fake Model -
$0 1 2 3 4 5 6 7 8 9
0 < . Recipient Send findViewWithText("4").click(); findViewWithText("2").click(); -
$0 1 2 3 4 5 6 7 8 9
0 < . Recipient Send findViewWithText("4").click(); findViewWithText("2").click(); findViewWithHint("Recipient").setText("[email protected]"); -
$0 1 2 3 4 5 6 7 8 9
0 < . Recipient Send findViewWithText("4").click(); findViewWithText("2").click(); findViewWithHint("Recipient").setText("[email protected]"); findViewWithText("Send").click(); -
$0 1 2 3 4 5 6 7 8 9
0 < . Recipient Send findViewWithText("4").click(); findViewWithText("2").click(); findViewWithHint("Recipient").setText("[email protected]"); findViewWithText("Send").click(); Thread.sleep(1000); -
$0 1 2 3 4 5 6 7 8 9
0 < . Recipient Send findViewWithText("4").click(); findViewWithText("2").click(); findViewWithHint("Recipient").setText("[email protected]"); findViewWithText("Send").click(); Thread.sleep(1000); findViewWithText("Success!"); -
$0 1 2 3 4 5 6 7 8 9
0 < . Recipient Send onView(withText("4")).perform(click()); onView(withText("2")).perform(click()); onView(withHint("Recipient")).perform(typeText("[email protected]")); onView(withText("Send")).perform(click()); onView(withText("Success!")).check(visible()); find W find W find W .set find W Thread.sleep(1000); find W -
$0 1 2 3 4 5 6 7 8 9
0 < . Recipient Send onView(withText("4")).perform(click()); onView(withText("2")).perform(click()); onView(withHint("Recipient")) .perform(typeText("[email protected]")); onView(withText("Send")).perform(click()); onView(withText("Success!")).check(visible()); Send $42 to "[email protected]". Verify success. -
$0 1 2 3 4 5 6 7 8 9
0 < . Recipient Send onView(withText("4")).perform(click()); onView(withText("2")).perform(click()); onView(withHint("Recipient")) .perform(typeText("[email protected]")); onView(withText("Send")).perform(click()); onView(withText("Success!")).verify(visible()); Send $42 to "[email protected]". Verify success. What How -
$0 1 2 3 4 5 6 7 8 9
0 < . Recipient Send class PaymentRobot { }A -
$0 1 2 3 4 5 6 7 8 9
0 < . Recipient Send class PaymentRobot { PaymentRobot amount(long amount) { // ... }B }A -
$0 1 2 3 4 5 6 7 8 9
0 < . Recipient Send class PaymentRobot { PaymentRobot amount(long amount) { // ... }B PaymentRobot recipient(String recipient) { // ... }C }A -
$0 1 2 3 4 5 6 7 8 9
0 < . Recipient Send class PaymentRobot { PaymentRobot amount(long amount) { // ... }B PaymentRobot recipient(String recipient) { // ... }C PaymentRobot send() { // ... }D }A -
$0 1 2 3 4 5 6 7 8 9
0 < . Recipient Send class PaymentRobot { PaymentRobot amount(long amount) { // ... }B PaymentRobot recipient(String recipient) { // ... }C ResultRobot send() { // ... }D }A class ResultRobot { }E Payment -
$0 1 2 3 4 5 6 7 8 9
0 < . Recipient Send class PaymentRobot { PaymentRobot amount(long amount) { // ... }B PaymentRobot recipient(String recipient) { // ... }C ResultRobot send() { // ... }D }A class ResultRobot { ResultRobot isSuccess() { // ... }F }E -
$0 1 2 3 4 5 6 7 8 9
0 < . Recipient Send class PaymentRobot { PaymentRobot amount(long amount) { long dollars = amount / 100; String dollarString = String.valueOf(dollars); for (int i = 0; i < dollarString.length(); i++) { onView(withText(dollarString.substring(i, i + 1))).perform(click()); } long cents = amount - (dollars * 100); if (cents != 0) { onView(withText(".")).perform(click()); String centsString = String.valueOf(cents); for (int i = 0; i < centsString.length(); i++) { onView(withText(centsString.substring(i, i + 1))).perform(click()); } } return this; }B PaymentRobot recipient(String recipient) { onView(withHint("Recipient")).perform(typeType("[email protected]")); return this; }C ResultRobot send() { onView(withText("Send")).perform(click()); return new ResultRobot(); }D }A class ResultRobot { ResultRobot isSuccess() { onView(withText("Success!")).verify(visible()); return this; }F }E -
$0 1 2 3 4 5 6 7 8 9
0 < . Recipient Send PaymentRobot payment = new PaymentRobot(); -
$0 1 2 3 4 5 6 7 8 9
0 < . Recipient Send PaymentRobot payment = new PaymentRobot(); payment.amount(42_00); -
$0 1 2 3 4 5 6 7 8 9
0 < . Recipient Send PaymentRobot payment = new PaymentRobot(); payment.amount(42_00); payment.recipient("[email protected]"); -
$0 1 2 3 4 5 6 7 8 9
0 < . Recipient Send PaymentRobot payment = new PaymentRobot(); payment.amount(42_00); payment.recipient("[email protected]") payment.send(); -
$0 1 2 3 4 5 6 7 8 9
0 < . Recipient Send PaymentRobot payment = new PaymentRobot(); payment.amount(42_00); payment.recipient("[email protected]") ResultRobot result = payment.send(); -
$0 1 2 3 4 5 6 7 8 9
0 < . Recipient Send PaymentRobot payment = new PaymentRobot(); payment.amount(42_00); payment.recipient("[email protected]") ResultRobot result = payment.send(); result.isSuccess(); -
$0 1 2 3 4 5 6 7 8 9
0 < . Recipient Send PaymentRobot payment = new PaymentRobot(); ResultRobot result = payment .amount(42_00) .recipient("[email protected]") .send(); result.isSuccess(); -
$0 1 2 3 4 5 6 7 8 9
0 < . Recipient Send PaymentRobot payment = new PaymentRobot(); ResultRobot result = payment .amount(42_00) .recipient("[email protected]") .send(); result.isSuccess(); Send $42 to "[email protected]". Verify success. -
$0 1 2 3 4 5 6 7 8 9
0 < . Recipient Send PaymentRobot payment = new PaymentRobot(); ResultRobot result = payment .amount(42_00) .recipient("[email protected]") .send(); result.isSuccess(); Send $42 to "[email protected]". Verify success. What -
$0 1 2 3 4 5 6 7 8 9
0 < . Recipient Send class PaymentRobot { PaymentRobot amount(long amount) { long dollars = amount / 100; String dollarString = String.valueOf(dollars); for (int i = 0; i < dollarString.length(); i++) { onView(withText(dollarString.substring(i, i + 1))).perform(click()); } long cents = amount - (dollars * 100); if (cents != 0) { onView(withText(".")).perform(click()); String centsString = String.valueOf(cents); for (int i = 0; i < centsString.length(); i++) { onView(withText(centsString.substring(i, i + 1))).perform(click()); } } return this; }B PaymentRobot recipient(String recipient) { onView(withHint("Recipient")).perform(typeType("[email protected]")); return this; }C ResultRobot send() { onView(withText("Send")).perform(click()); return new ResultRobot(); }D }A class ResultRobot { ResultRobot isSuccess() { onView(withText("Success!")).verify(visible()); return this; }F }E How -
$0 1 2 3 4 5 6 7 8 9
0 < . Recipient Send PaymentRobot payment = new PaymentRobot(); ResultRobot result = payment .amount(42_00) .recipient("[email protected]") .send(); result.isSuccess(); -
$0 1 2 3 4 5 6 7 8 9
0 < . Recipient Send @Test public void singleFundingSourceSuccess() { PaymentRobot payment = new PaymentRobot(); ResultRobot result = payment .amount(42_00) .recipient("[email protected]") .send(); result.isSuccess(); }A -
$0 1 2 3 4 5 6 7 8 9
0 < . Recipient Send @Test public void singleFundingSourceSuccess() { PaymentRobot payment = new PaymentRobot(); ResultRobot result = payment .amount(42_00) .recipient("[email protected]") .send(); result.isSuccess(); }A @Test public void singleFundingSourceTooMuch() { PaymentRobot payment = new PaymentRobot(); ResultRobot result = payment .amount(1_000_000_00) .recipient("[email protected]") .send(); result.isSuccess(); }B -
$0 1 2 3 4 5 6 7 8 9
0 < . Recipient Send @Test public void singleFundingSourceSuccess() { PaymentRobot payment = new PaymentRobot(); ResultRobot result = payment .amount(42_00) .recipient("[email protected]") .send(); result.isSuccess(); }A @Test public void singleFundingSourceTooMuch() { PaymentRobot payment = new PaymentRobot(); ResultRobot result = payment .amount(1_000_000_00) .recipient("[email protected]") .send(); result.isSuccess(); }B @Test public void singleFundingSourceInsufficientFunds() { PaymentRobot payment = new PaymentRobot(); ResultRobot result = payment .amount(1_000_00) .recipient("[email protected]") .send(); result.isSuccess(); }C -
@Test public void singleFundingSourceSuccess() { PaymentRobot payment = new PaymentRobot();
ResultRobot result = payment .amount(42_00) .recipient("[email protected]") .send(); result.isSuccess(); }A @Test public void singleFundingSourceTooMuch() { PaymentRobot payment = new PaymentRobot(); ResultRobot result = payment .amount(1_000_000_00) .recipient("[email protected]") .send(); result.isSuccess(); }B @Test public void singleFundingSourceInsufficientFunds() { PaymentRobot payment = new PaymentRobot(); ResultRobot result = payment .amount(1_000_00) .recipient("[email protected]") .send(); result.isSuccess(); }C @Test public void networkErrorAllowsRetry() { // ... } @Test public void multipleFundingSourcesSuccess() { // ... } @Test public void multipleFundingSourcesOneDisabled() { // ... } @Test public void multipleFundingSourcesInsufficientUsesOther() { // ... } @Test public void multipleFundingSourcesAllInsufficient() { // ... } @Test public void unauthenticatedLogsOut() { // ... } @Test public void rotationRetainsAmountAndRecipient() { // ... } @Test public void serverErrorShowsTryAgainLater() { // ... } -
Presenter View Fake Model Robot Test Test Test Test Test
Test Test Test Test Test Test -
PaymentRobot payment = new PaymentRobot(); ResultRobot result = payment .amount(42_00)
.recipient("[email protected]") .send(); result.isSuccess(); -
class PaymentRobot { PaymentRobot amount(long amount) {A // ... }B
PaymentRobot recipient(String recipient) {B // ... }C ResultRobot send() {C // ... }D }A class ResultRobot { ResultRobot isSuccess() {D // ... }F }E -
class PaymentRobot { fun amount(amount: Long): PaymentRobot {A // ...
}B fun recipient(recipient: String): PaymentRobot {B // ... }C fun send(): ResultRobot {C // ... }D }A class ResultRobot { fun isSuccess(): ResultRobot {D // ... }F }E -
val val PaymentRobot payment = new PaymentRobot(); ResultRobot result
= payment .amount(42_00) .recipient("[email protected]") .send(); result.isSuccess(); -
val payment = PaymentRobot() val result = payment .amount(4200)
.recipient("[email protected]") .send() result.isSuccess() -
class PaymentRobot { fun amount(amount: Long): PaymentRobot {A // ...
}B fun recipient(recipient: String): PaymentRobot {B // ... }C fun send(): ResultRobot {C // ... }D }A class ResultRobot { fun isSuccess(): ResultRobot {D // ... }F }E -
class PaymentRobot { fun amount(amount: Long) {A // ... }B
fun recipient(recipient: String) {B // ... }C fun send(): ResultRobot {C // ... }D }A class ResultRobot { fun isSuccess() {D // ... }F }E -
fun payment() = PaymentRobot() class PaymentRobot { fun amount(amount: Long)
{A // ... }B fun recipient(recipient: String) {B // ... }C fun send(): ResultRobot {C // ... }D }A class ResultRobot { fun isSuccess() {D // ... }F }E -
fun payment(func: PaymentRobot.() -> Unit) = PaymentRobot() class PaymentRobot {
fun amount(amount: Long) {A // ... }B fun recipient(recipient: String) {B // ... }C fun send(): ResultRobot {C // ... }D }A class ResultRobot { fun isSuccess() {D // ... }F }E -
fun payment(func: PaymentRobot.() -> Unit) = PaymentRobot().apply { func() }
class PaymentRobot { fun amount(amount: Long) {A // ... }B fun recipient(recipient: String) {B // ... }C fun send(): ResultRobot {C // ... }D }A class ResultRobot { fun isSuccess() {D // ... }F }E -
fun payment(func: PaymentRobot.() -> Unit) = PaymentRobot().apply { func() }
class PaymentRobot { fun amount(amount: Long) {A // ... }B fun recipient(recipient: String) {B // ... }C fun send(): ResultRobot {C // ... }D }A class ResultRobot { fun isSuccess() {D // ... }F }E -
val payment = PaymentRobot() val result = payment .amount(4200)
.recipient("[email protected]") .send() result.isSuccess() -
fun payment(func: PaymentRobot.() -> Unit) = PaymentRobot().apply { func() }
class PaymentRobot { fun amount(amount: Long) {A // ... }B fun recipient(recipient: String) {B // ... }C fun send(): ResultRobot {C // ... }D }A class ResultRobot { fun isSuccess() {D // ... }F }E -
fun payment(func: PaymentRobot.() -> Unit) = PaymentRobot().apply { func() }
class PaymentRobot { fun amount(amount: Long) {A // ... }B fun recipient(recipient: String) {B // ... }C fun send(func: ResultRobot.() -> Unit): ResultRobot {C // ... return ResultRobot().apply { func() } }D }A class ResultRobot { fun isSuccess() {D // ... }F }E -
fun payment(func: PaymentRobot.() -> Unit) = PaymentRobot().apply { func() }
class PaymentRobot { fun amount(amount: Long) {A // ... }B fun recipient(recipient: String) {B // ... }C fun send(func: ResultRobot.() -> Unit): ResultRobot {C // ... return ResultRobot().apply { func() } }D }A class ResultRobot { fun isSuccess() {D // ... }F }E -
fun payment(func: PaymentRobot.() -> Unit) = PaymentRobot().apply { func() }
class PaymentRobot { fun amount(amount: Long) {A // ... }B fun recipient(recipient: String) {B // ... }C fun send(func: ResultRobot.() -> Unit): ResultRobot {C // ... return ResultRobot().apply { func() } }D }A class ResultRobot { fun isSuccess() {D // ... }F }E -
fun payment(func: PaymentRobot.() -> Unit) = PaymentRobot().apply { func() }
class PaymentRobot { fun amount(amount: Long) {A // ... }B fun recipient(recipient: String) {B // ... }C infix fun send(func: ResultRobot.() -> Unit): ResultRobot {C // ... return ResultRobot().apply { func() } }D }A class ResultRobot { fun isSuccess() {D // ... }F }E -
fun payment(func: PaymentRobot.() -> Unit) = PaymentRobot().apply { func() }
class PaymentRobot { fun amount(amount: Long) {A // ... }B fun recipient(recipient: String) {B // ... }C infix fun send(func: ResultRobot.() -> Unit): ResultRobot {C // ... return ResultRobot().apply { func() } }D }A class ResultRobot { fun isSuccess() {D // ... }F }E -
payment { amount(4200) recipient("[email protected]") } send { isSuccess() }A
Screen entry point Actions and/or assertions Screen transition Actions and/or assertions -
Exception in thread "main" java.lang.AssertionError: Expected <Success!> but found <Failed!>
at ResultRobot.isSuccess(ResultRobot.kt:18) at PaymentTest$singleFundingSourceSuccess$2.invoke(PaymentTest.kt:27) at PaymentRobot.send(PaymentRobot.kt:13) at PaymentTest.singleFundingSourceSuccess(PaymentTest.kt:8) -
payment { amount(4200) recipient("[email protected]") send() } birthday { date(1970, 1,
1) next() } ssn { value("123-56-7890") next() } result { isSuccess() }A -
fun payment(func: PaymentRobot.() -> Unit) { onView(withText("$0")).check(visible()) return PaymentRobot().apply {
func() } } fun birthdate(func: BirthdateRobot.() -> Unit) { onView(withText("Enter birth date:")).check(visible()) return BirthdateRobot().apply { func() } } fun ssn(func: SsnRobot.() -> Unit) { onView(withText("Enter social security number:")).check(visible()) return SsnRobot().apply { func() } } fun result(func: ResultRobot.() -> Unit) { return ResultRobot().apply { func() } }