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. 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
  2. 7.
  3. 9.

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

    http://martinfowler.com/eaaDev/SupervisingPresenter.html http://martinfowler.com/eaaDev/
  4. 14.
  5. 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)
  6. 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)
  7. 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
  8. 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_!@#"                );  
  9. 19.

    LoginPresenter.java .getErrorFor(code) Presenter LoginPresenter  presenter  =        

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

     public  class  TaskCreatePresenter  {
            

     }        public  String  getDueDateText(                                              DateTime  dueDateTime){...}   Example 1 Fuzzy due dates      public  String  getDueDateDiffText(                                            DateTime  dueDateTime)  {...}  
  11. 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");  
  12. 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
  13. 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
  14. 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
  15. 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();
 }
  16. 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
  17. 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
  18. 30.
  19. 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
  20. 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);

  21. 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

  22. 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
  23. 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)  {               }   }
  24. 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