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

Painless UI Testing

Painless UI Testing

What if you could get your UI tests to run as fast as your unit tests?

Using patterns of yore (like Martin Fowler's supervising controller, effective use of presenters and view model state) we're going to tackle everyday-real-annoying impediments to UI testing. We'll address what parts of the UI need testing and effective ways of testing them.

Kaushik Gopal

August 28, 2015
Tweet

More Decks by Kaushik Gopal

Other Decks in Programming

Transcript

  1. Painless UI Testing
    Kaushik Gopal
    Fragmented

    View full-size slide

  2. Agenda
    Painful UI Testing
    Suggested pattern
    Concrete examples
    GRAND REVEAL!
    1
    2
    3

    View full-size slide

  3. 1 Painful UI Testing
    Why should I even test my
    UI?

    View full-size slide

  4. 1 Painful UI Testing
    Why is it Painful?

    View full-size slide

  5. Why is it Painful?
    Human beings suck at repetitive
    work
    courtesy: soundtrackdreams.com

    View full-size slide

  6. Why is it Painful?
    Human beings suck at repetitive work
    GUIs are tightly linked to the framework
    Tools and framework
    Speed - Test feedback loop

    View full-size slide

  7. Try testing as much as you can
    off the device
    2 Suggestions?
    Use the JVM!

    View full-size slide

  8. 2 Proposed pattern
    MVC
    MVP
    MVVP
    MV?
    WT*

    View full-size slide

  9. • Passive View
    • Supervising Controller
    The Fowler way!
    http://martinfowler.com/eaaDev/PassiveScreen.html
    http://martinfowler.com/eaaDev/SupervisingPresenter.html
    http://martinfowler.com/eaaDev/

    View full-size slide

  10. Activity
    The Fowler way!
    Controller
    onTextChange
    Presenter
    View
    Interface

    View full-size slide

  11. Activity
    The Fowler way!
    Controller
    onTextChange
    onButtonClick
    onRefreshScreen
    Presenter
    View
    Interface

    View full-size slide

  12. Activity
    The Fowler way!
    Controller
    onTextChange
    Presenter
    View
    Interface
    View Model
    Service
    POJO

    View full-size slide

  13. Concrete Examples
    3

    View full-size slide

  14. LOGIN
    Login
    Activity
    Button

    .OnClick()
    LoginController.java
    .onLoginButtonClicked(
    username,
    password)
    interface
    ITalkToLoginScreen
    .showError(errorMsg)

    .moveToWelcomeScreen()

    Activity Controller
    View Interface
    AuthService.java
    .checkUsername()

    .checkPassword()
    Some Service
    Presenter
    implements
    ITalkToLoginScreen


    .showError
    -> setText(errorMsg)

    .moveToWelcomeScreen
    -> switch Fragments
    LoginPresenter.
    java
    .getErrorFor(code)

    View full-size slide

  15. JVM Testable
    LOGIN
    Login
    Activity
    Button

    .OnClick()
    LoginController.java
    .onLoginButtonClicked(
    username,
    password)
    interface
    ITalkToLoginScreen
    .showError(errorMsg)

    .moveToWelcomeScreen()

    Activity Controller
    View Interface
    AuthService.java
    .checkUsername()

    .checkPassword()
    Some Service
    Presenter
    implements
    ITalkToLoginScreen


    .showError
    -> setText(errorMsg)

    .moveToWelcomeScreen
    -> switch Fragments
    LoginPresenter.
    java
    .getErrorFor(code)

    View full-size slide

  16. LoginController.java
    .onLoginButtonClicked(
    username,
    password)
    ITalkToLoginScreen.java
    .showError(errorMsg)

    .moveToWelcomeScreen()

    Controller
    View Interface
    controller  =    
                 new  LoginController(talkToLsMock);  
    verify(_talkToTLSMock)  
               .showError("Asking  to  get  hacked?");  
     controller  
                 .onLoginButtonClicked(  
                           "[email protected]",  
                           "superWeakPassword"  
                 );  
    controller  =    
                 new  LoginController(talkToLsMock);  
     controller  
                 .onLoginButtonClicked(  
                           "[email protected]",  
                           "superWeakPassword"  
                 );  
    R.string.error_message  
    @Test

    View full-size slide

  17. LoginController.java
    .onLoginButtonClicked(
    username,
    password)
    ITalkToLoginScreen.java
    .showError(errorMsg)

    .moveToWelcomeScreen()

    Controller
    View Interface
    verify(_talkToTLSMock)  
                 .moveToWelcomeScreen();  
    @Test
    controller  =    
                 new  LoginController(talkToLsMock);  
     controller  
                 .onLoginButtonClicked(  
                           "[email protected]",  
                           "M4Y_the-­‐4Ce_Be_W1tH_U_!@#"  
                 );  
     controller  
                 .onLoginButtonClicked(  
                           "[email protected]",  
                           "M4Y_the-­‐4Ce_Be_W1tH_U_!@#"  
                 );  

    View full-size slide

  18. LoginPresenter.java
    .getErrorFor(code)
    Presenter
    LoginPresenter  presenter  =    
                                       new  TaskListPresenter();  
    assertThat(presenter.getErrorCode(enumCode)  
                               .isEqualTo("[Wed]  9:00  AM  [1985]");  

    View full-size slide

  19. Introducing Remme

    View full-size slide

  20. Example 1 Fuzzy due dates

    View full-size slide

  21.  public  class  TaskCreatePresenter  {

               
     }  
         public  String  getDueDateText(  
                                               DateTime  dueDateTime){...}  
    Example 1 Fuzzy due dates
         public  String  getDueDateDiffText(  
                                             DateTime  dueDateTime)  {...}  

    View full-size slide

  22. @Test

    public  void  IncreaseBy_6HRS()  {

     

     TaskCreatePresenter  taskCreatePresenter  =    
                                           new  TaskCreatePresenter();  
    }  
    assertThat(taskCreatePresenter  
                             .getDueDateText(dueDate))  
         
         .isEqualTo("Aug  25  [Tue]  12:58  AM");  
    assertThat(taskCreatePresenter  
                                           .getDueDateDiffText(dueDate)  
                             
                         .isEqualTo("(~6  Hrs)");

    Example 1 Fuzzy due dates
    DateTime  dueDate  =  now.plus6Hours();  
    @Test

    public  void  IncreaseBy_6HRS()  {

     

     TaskCreatePresenter  taskCreatePresenter  =    
                                           new  TaskCreatePresenter();  
    }  
    DateTime  dueDate  =  now.plus6Hours();  
    assertThat(taskCreatePresenter  
                             .getDueDateText(dueDate))  
         
         .isEqualTo("Aug  25  [Tue]  12:58  AM");  

    View full-size slide

  23. TaskCreateFragment
    @OnClick(R.id.increase_hr)
    @OnClick(R.id.increase_hr)

    public  void  onIncreaseBy1HrClicked()  {

           taskCreateController.changeDueDateBy(PLUS,  HOURS,  1);

    }  
    Example 1 Fuzzy due dates

    View full-size slide

  24. TaskCreateFragment
    @OnClick(R.id.increase_hr)
    TaskCreateController.java
    public  void  changeDueDateBy(...)  {  
       dueDateTime  =  now  +  changes  in  time;    
       String  dueDateText  =    (from  presenter)  
       String  dueDateDiffText  =  (from  presenter)  

       iTalkToTaskCreateScreen  (view  interface)  
    .updateDueDateDisplay(dueDateText,    
                                               dueDateDiffText);  
    }
    Example 1 Fuzzy due dates

    View full-size slide

  25. TaskCreateFragment
    @OnClick(R.id.increase_hr)
    TaskCreateController.java
    void changeDueDateBy() {
    iTalkToTaskCreateScreen
    .updateDueDateDisplay()
    }
    interface
    ITalkToTaskCreateScreen.java
    .updateDueDateDisplay()

    .closeCreateScreen()

    Example 1 Fuzzy due dates
    TaskCreatePresenter
    TaskCreatePresenter.
    java

    View full-size slide

  26. TaskCreatePresenter
    TaskCreatePresenter.
    java
    TaskCreateFragment
    @OnClick(R.id.increase_hr)
    TaskCreateController.java
    void changeDueDateBy() {
    iTalkToTaskCreateScreen
    .updateDueDateDisplay()
    }
    Example 1 Fuzzy due dates
    public  interface  ITalkToTaskCreateScreen  {  

           void  updateDueDateDisplay(  
                             String  dueDateText,    
                             String  dueDiffDisplayText);

           void  closeCreateScreen();

    }

    View full-size slide

  27. Fuzzy due dates
    TaskCreateFragment
    @OnClick(R.id.increase_hr)
    TaskCreateController.java
    void changeDueDateBy() {
    iTalkToTaskCreateScreen
    .updateDueDateDisplay()
    }
    interface
    ITalkToTaskCreateScreen.java
    .updateDueDateDisplay()

    .closeCreateScreen()

    implements
    ITalkToTaskCreateScreen


    .updateDueDateDisplay
    -> setText(date + diff)

    .closeCreateScreen
    -> popBackStack
    Example 1
    TaskCreatePresenter
    TaskCreatePresenter.
    java

    View full-size slide

  28. implements
    ITalkToTaskCreateScreen


    .updateDueDateDisplay
    -> setText(date + diff)

    .closeCreateScreen
    -> popBackStack
    TaskCreateFragment
    @OnClick(R.id.increase_hr)
    TaskCreateController.java
    void changeDueDateBy() {
    iTalkToTaskCreateScreen
    .updateDueDateDisplay()
    }
    interface
    ITalkToTaskCreateScreen
    .updateDueDateDisplay()

    .closeCreateScreen()

    @Override

    public  void  updateDueDateDisplay(dueDateText,  dueDiffDisplayText)  {

           _dueDisplay.setText(dueDateText  +  dueDiffDisplayText);

    }


    @Override

    public  void  closeCreateScreen()  {

           getFragmentManager().popBackStack();

    }
    Example 1 Fuzzy due dates
    TaskCreatePresenter
    TaskCreatePresenter.
    java

    View full-size slide

  29. Example 2 Alternate & Distinct Week coloring
    Week 1
    Week 2
    Week 4
    Week 5
    Week 0
    Week 6
    Week 10
    Week 12
    1
    2
    Consecutive weeks - different color
    Not necessarily directly consecutive

    View full-size slide

  30. Example 2 Alternate & Distinct Week coloring
    1 Consecutive weeks - different color
    @Test

    public  void  DueDateTimeColor_ShouldAlternateBetweenDifferentWeeks()  {

           DateTime  someFutureDate  =  ...;

             
    }  
    @Test

    public  void  DueDateTimeColor_ShouldAlternateBetweenDifferentWeeks()  {

           DateTime  someFutureDate  =  ...;

             
    }  
         List  tasks  =  new  ArrayList<>();
    Task  t1  =  aTask().withDueDate(getDateFor(someFutureDate))  
    Task  t2  =  aTask().withDueDate(getDateFor(someFutureDate.plusDays(8)))  
           tasks.add(t1);    
           tasks.add(t2);  
         

           int  firstColor  =  presenter.getDueDayTimeColorIdFor(tasks,  0);  
           int  secondColor  =  presenter.getDueDayTimeColorIdFor(tasks,  1);

           assertThat(firstColor).isNotEqualTo(secondColor);
         List  tasks  =  new  ArrayList<>();
    Task  t1  =  aTask().withDueDate(getDateFor(someFutureDate))  
    Task  t2  =  aTask().withDueDate(getDateFor(someFutureDate.plusDays(8)))  
           tasks.add(t1);    
           tasks.add(t2);  
         

           int  firstColor  =  presenter.getDueDayTimeColorIdFor(tasks,  0);  
           int  secondColor  =  presenter.getDueDayTimeColorIdFor(tasks,  1);


    View full-size slide

  31. Example 2 Alternate & Distinct Week coloring
    2 Not necessarily directly consecutive
    @Test

    public  void  
    DueDateTimeColor_ShouldAlternateBetweenDifferent_NotNecessarilyConsecutiveWeeks()  {

    }
           int  taskColor1  =  presenter.getDueDayTimeColorIdFor(tasks,  0);

           int  taskColor2  =  presenter.getDueDayTimeColorIdFor(tasks,  1);

           int  taskColor3  =  presenter.getDueDayTimeColorIdFor(tasks,  2);

           int  taskColor4  =  presenter.getDueDayTimeColorIdFor(tasks,  3);

           //  ....

    @Test

    public  void  
    DueDateTimeColor_ShouldAlternateBetweenDifferent_NotNecessarilyConsecutiveWeeks()  {

    }
         DateTime  someFutureDate  =  ...;

           List  tasks  =  new  ArrayList<>();
           tasks.add(aTask().withDueDate(getDateFor(someFutureDate)).build());                          //  t1

           tasks.add(aTask().withDueDate(getDateFor(someFutureDate.plusDays(6))).build());  //  t2

           tasks.add(aTask().withDueDate(getDateFor(someFutureDate.plusDays(7))).build());  //  t3

           tasks.add(aTask().withDueDate(getDateFor(someFutureDate.plusDays(8))).build());  //  t4

           tasks.add(aTask().withDueDate(getDateFor(someFutureDate.plusDays(9))).build());  //  t5

           tasks.add(aTask().withDueDate(getDateFor(someFutureDate.plusDays(12))).build());//  t6

           tasks.add(aTask().withDueDate(getDateFor(someFutureDate.plusDays(13))).build());//  t7

           tasks.add(aTask().withDueDate(getDateFor(someFutureDate.plusDays(14))).build());//  t8

           tasks.add(aTask().withDueDate(getDateFor(someFutureDate.plusDays(28))).build());//  t9

         DateTime  someFutureDate  =  ...;

           List  tasks  =  new  ArrayList<>();
           tasks.add(aTask().withDueDate(getDateFor(someFutureDate)).build());                          //  t1

           tasks.add(aTask().withDueDate(getDateFor(someFutureDate.plusDays(6))).build());  //  t2

           tasks.add(aTask().withDueDate(getDateFor(someFutureDate.plusDays(7))).build());  //  t3

           tasks.add(aTask().withDueDate(getDateFor(someFutureDate.plusDays(8))).build());  //  t4

           tasks.add(aTask().withDueDate(getDateFor(someFutureDate.plusDays(9))).build());  //  t5

           tasks.add(aTask().withDueDate(getDateFor(someFutureDate.plusDays(12))).build());//  t6

           tasks.add(aTask().withDueDate(getDateFor(someFutureDate.plusDays(13))).build());//  t7

           tasks.add(aTask().withDueDate(getDateFor(someFutureDate.plusDays(14))).build());//  t8

           tasks.add(aTask().withDueDate(getDateFor(someFutureDate.plusDays(28))).build());//  t9


    View full-size slide

  32. Example 2 Alternate & Distinct Week coloring
    2 Not necessarily directly consecutive
           assertThat(taskColor1).isEqualTo(taskColor2);          //  6  day  difference  (within  week)


           assertThat(taskColor2).isNotEqualTo(taskColor3);    //  1  day  diff  (crosses  to  next  week)

     

           assertThat(taskColor3).isEqualTo(taskColor4);          //  1  day  diff  (same  week)

           assertThat(taskColor4).isEqualTo(taskColor5);          //  1  day  diff  (same  week)

           assertThat(taskColor5).isEqualTo(taskColor6);          //  1  day  diff  (same  week)

           assertThat(taskColor6).isEqualTo(taskColor7);          //  1  day  diff  (same  week)


           assertThat(taskColor7).isNotEqualTo(taskColor8);    //  cross  to  next  week


           assertThat(taskColor8).isNotEqualTo(taskColor9);    //  cross  to  2  weeks  from  last  task
           tasks.add(aTask().withDueDate(getDateFor(someFutureDate)).build());                        
           tasks.add(aTask().withDueDate(getDateFor(someFutureDate.plusDays(6))).build());
           tasks.add(aTask().withDueDate(getDateFor(someFutureDate.plusDays(7))).build());  //  t3
           tasks.add(aTask().withDueDate(getDateFor(someFutureDate.plusDays(8))).build());  //  t4
           tasks.add(aTask().withDueDate(getDateFor(someFutureDate.plusDays(9))).build());  //  t5
           tasks.add(aTask().withDueDate(getDateFor(someFutureDate.plusDays(12))).build());//  t6
           tasks.add(aTask().withDueDate(getDateFor(someFutureDate.plusDays(13))).build());//  t7
           tasks.add(aTask().withDueDate(getDateFor(someFutureDate.plusDays(14))).build());//  t8
           tasks.add(aTask().withDueDate(getDateFor(someFutureDate.plusDays(28))).build());//  t9

    View full-size slide

  33. Example 2 Alternate & Distinct Week coloring
    2 Not necessarily directly consecutive
    public  class  TaskListPresenter  {  
    @ColorRes

    public  int  getDueDayTimeColorIdFor(List  tasks,  int  position)  {  
               
    }  
    }
    //  or  
         return  R.color.blue;  
    //  or  
         return  R.color.gray_2;  
    //  write  the  fancy  logic  and  
    //  ....  
         return  R.color.orange_1;
    public  class  TaskListPresenter  {  
    @ColorRes

    public  int  getDueDayTimeColorIdFor(List  tasks,  int  position)  {  
               
    }  
    }

    View full-size slide

  34. More examples
    github.com/kaushikgopal/rem
    still private
    send me a tweet @kaushikgopal
    and i'll add you to the repo
    3

    View full-size slide

  35. Conclusion
    • I mean... would you really do this?
    • So no Espresso ?
    • Data Binding?
    • Some things are just plain hard to test
    • Flaky tests are worse than no tests
    • Don't test Android SDK or libraries

    View full-size slide

  36. Questions?
    @kaushikgopal

    View full-size slide