Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Testing Robots (Kotlin Night, May 2016)

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

May 17, 2016
Tweet

More Decks by Jake Wharton

Other Decks in Programming

Transcript

  1. $0 1 2 3 4 5 6 7 8 9

    0 < . Recipient Send
  2. • 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.
  3. 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
  4. 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
  5. 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
  6. 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
  7. 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
  8. $0 1 2 3 4 5 6 7 8 9

    0 < . Recipient Send findViewWithText("4").click();
 findViewWithText("2").click();
  9. $0 1 2 3 4 5 6 7 8 9

    0 < . Recipient Send findViewWithText("4").click();
 findViewWithText("2").click();
 findViewWithHint("Recipient").setText("[email protected]");
  10. $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();
  11. $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);
  12. $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!");
  13. $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
  14. $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.
  15. $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
  16. $0 1 2 3 4 5 6 7 8 9

    0 < . Recipient Send class PaymentRobot {
 }A
  17. $0 1 2 3 4 5 6 7 8 9

    0 < . Recipient Send class PaymentRobot {
 PaymentRobot amount(long amount) {
 // ...
 }B
 }A
  18. $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
  19. $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
  20. $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
  21. $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
  22. $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
  23. $0 1 2 3 4 5 6 7 8 9

    0 < . Recipient Send PaymentRobot payment = new PaymentRobot();
  24. $0 1 2 3 4 5 6 7 8 9

    0 < . Recipient Send PaymentRobot payment = new PaymentRobot();
 payment.amount(42_00);
  25. $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]");
  26. $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();
  27. $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();
  28. $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();
  29. $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();
  30. $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.
  31. $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
  32. $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
  33. $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();
  34. $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
  35. $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
  36. $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
  37. @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() {
 // ...
 }
  38. 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
  39. 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
  40. val
 
 val PaymentRobot payment = new PaymentRobot();
 ResultRobot result

    = payment
 .amount(42_00)
 .recipient("[email protected]")
 .send();
 result.isSuccess();
  41. 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
  42. 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
  43. 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
  44. 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
  45. 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
  46. 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
  47. 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
  48. 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
  49. 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
  50. 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
  51. 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
  52. 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
  53. payment { 
 amount(4200)
 recipient("[email protected]")
 } send {
 isSuccess()
 }A

    Screen entry point Actions and/or assertions Screen transition Actions and/or assertions
  54. 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)
  55. payment {
 amount(4200)
 recipient("[email protected]") send() } birthday { date(1970, 1,

    1) next() } ssn { value("123-56-7890") next() } result { isSuccess() }A
  56. 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() } }