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.

A487b8723907637cb1af973bc5957bb4?s=128

Kaushik Gopal

August 28, 2015
Tweet

Transcript

  1. Painless UI Testing Kaushik Gopal Fragmented

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

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

    UI?
  4. 1 Painful UI Testing Why is it Painful?

  5. Why is it Painful? Human beings suck at repetitive work

    courtesy: soundtrackdreams.com
  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
  7. Try testing as much as you can off the device

    2 Suggestions? Use the JVM!
  8. 2 Proposed pattern MVC MVP MVVP MV? WT*

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

    http://martinfowler.com/eaaDev/SupervisingPresenter.html http://martinfowler.com/eaaDev/
  10. Activity The Fowler way! Controller onTextChange Presenter View Interface

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

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

    Model Service POJO
  13. Concrete Examples 3

  14. LOGIN

  15. 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)
  16. 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)
  17. 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(                          "iamcool@aol.com",                          "superWeakPassword"                );   controller  =                  new  LoginController(talkToLsMock);    controller                .onLoginButtonClicked(                          "iamcool@aol.com",                          "superWeakPassword"                );   R.string.error_message   @Test
  18. LoginController.java .onLoginButtonClicked( username, password) ITalkToLoginScreen.java .showError(errorMsg)
 .moveToWelcomeScreen()
 Controller View Interface

    verify(_talkToTLSMock)                .moveToWelcomeScreen();   @Test controller  =                  new  LoginController(talkToLsMock);    controller                .onLoginButtonClicked(                          "iamcool@aol.com",                          "M4Y_the-­‐4Ce_Be_W1tH_U_!@#"                );    controller                .onLoginButtonClicked(                          "iamcool@aol.com",                          "M4Y_the-­‐4Ce_Be_W1tH_U_!@#"                );  
  19. LoginPresenter.java .getErrorFor(code) Presenter LoginPresenter  presenter  =        

                                   new  TaskListPresenter();   assertThat(presenter.getErrorCode(enumCode)                              .isEqualTo("[Wed]  9:00  AM  [1985]");  
  20. Introducing Remme

  21. Example 1 Fuzzy due dates

  22.  public  class  TaskCreatePresenter  {
            

     }        public  String  getDueDateText(                                              DateTime  dueDateTime){...}   Example 1 Fuzzy due dates      public  String  getDueDateDiffText(                                            DateTime  dueDateTime)  {...}  
  23. @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");  
  24. TaskCreateFragment @OnClick(R.id.increase_hr) @OnClick(R.id.increase_hr)
 public  void  onIncreaseBy1HrClicked()  {
      

     taskCreateController.changeDueDateBy(PLUS,  HOURS,  1);
 }   Example 1 Fuzzy due dates
  25. 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
  26. 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
  27. 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();
 }
  28. 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
  29. 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
  30. Example 2

  31. 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
  32. 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<Task>  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<Task>  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);

  33. 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<Task>  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<Task>  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

  34. 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
  35. Example 2 Alternate & Distinct Week coloring 2 Not necessarily

    directly consecutive public  class  TaskListPresenter  {   @ColorRes
 public  int  getDueDayTimeColorIdFor(List<Task>  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<Task>  tasks,  int  position)  {               }   }
  36. More examples github.com/kaushikgopal/rem still private send me a tweet @kaushikgopal

    and i'll add you to the repo 3
  37. 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
  38. Questions? @kaushikgopal