Slide 1

Slide 1 text

Painless UI Testing Kaushik Gopal Fragmented

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

1 Painful UI Testing Why is it Painful?

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

2 Proposed pattern MVC MVP MVVP MV? WT*

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

Activity The Fowler way! Controller onTextChange Presenter View Interface

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

Concrete Examples 3

Slide 14

Slide 14 text

LOGIN

Slide 15

Slide 15 text

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)

Slide 16

Slide 16 text

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)

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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_!@#"                );  

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

Introducing Remme

Slide 21

Slide 21 text

Example 1 Fuzzy due dates

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

@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");  

Slide 24

Slide 24 text

TaskCreateFragment @OnClick(R.id.increase_hr) @OnClick(R.id.increase_hr)
 public  void  onIncreaseBy1HrClicked()  {
        taskCreateController.changeDueDateBy(PLUS,  HOURS,  1);
 }   Example 1 Fuzzy due dates

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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();
 }

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

Example 2

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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);


Slide 33

Slide 33 text

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


Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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)  {               }   }

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

Questions? @kaushikgopal