Designing for testability with MVP and Activities and Places

265 views
Skip to first unread message

Mike Dee

unread,
Jun 12, 2012, 8:59:33 PM6/12/12
to google-we...@googlegroups.com
Here is a simple example that I'm working on.  Input would be appreciated.  I'm sure there are different approaches, but I'm having trouble finding one and having a particular issue with the level of granularity of testing.

I know one answer, in advance, will be to not use GWTTestCase.  I will heed that.  But the example below represents a simpler situation where GWTTestCase will likely be needed.  So, how would one do this with GWTTestCase (assuming the actual case I'll be testing for is a good candidate for GWTTestCase)?

I have a basic search form.  It has fields on it like: first name, last name, etc.  There is also a Find button.  When the Find button is pressed, a list of people matching the criteria appear.  Each row in the list (CellTable) has person's name, date of birth, address, etc.   Many items are links (Hyperlinks).  For example, the person's name is a Hyperlink that, when clicked, goes to a new screen showing the person details.  Basic CRUD stuff.

There is a presenter, defined by the view.

public interface PersonSearchView extends IsWidget
{
    public void setPresenter( Presenter listener );
    public void setData( Vector<PersonSearchInfo> psinfos );
    ...

    public interface Presenter
    {
          /**
         * Find people that match certain criteria.
         *
         * @return
         */
        void findPersons( String firstname, String lastname, int sortBy, int start, int cnt );

        /**
         * Navigate to a new Place in the browser.
         */
        void goTo( Place place );
    }
}

The PersonSearchActivity implements the Presenter.

public class PersonSearchActivity extends AbstractActivity implements PersonSearchView.Presenter
{
     ...
    public void findPersons( String firstname, String lastname, int sortBy, int start, int cnt )
    {
        MyServiceAsync srv = GWT.create( MyService.class );
        PersonSearchCallback personSearchCallback = new PersonSearchCallback ();
        srv .findPersons( firstname, lastname, sortBy, start, cnt, personSearchCallback );
    }

    private class PersonSearchCallback extends AsyncCallback<Vector<PersonSearchInfo>>
    {
        public void onFail( Throwable caught )
        {
             caught.printStackTrace();
        }

        public void onSuccess( Vector<PersonSearchInfo> results )
        {
             clientFactory.getPersonSearchView().setData( results );
        }
    }
}

The PersonSearchViewImpl has a bunch of test fields on it (firstname, lastname, find button, etc) and calls the activities method to actually do work.

public class PersonSearchViewImpl extends ResizeComposite implements PersonSearchView
{
    interface Binder extends UiBinder<Widget, PersonSearchViewImpl>
    {
    }

    private static final Binder binder = GWT.create( Binder.class );

    @UiField TextBox firstname;
    @UiField TextBox lastname;
    @UiField Button findButton;

    /**
     * Handle Search button - do the search.
     */
    @UiHandler( "findButton" )
    protected void onFindButtonClick( ClickEvent event )
    {
        ...
        listener.findPersons( firstname, lastname, sortby, start, cnt );
        ...
    }
}

A couple of questions regarding a test I'd like to perform.  Let's assume that testing occurs with a live database (and GWT-RPC).  I know that contest of the database and what should be returned for certain queries.  I'd like to run tests that execute certain queries and make sure they appear in the table (at certain positions).

1) How would I test the clicking within the results.  For example, each person's name appears (per row) as a Hyperlink in the results.  This is implemented in a CellTable with Hyperlinks.  Normal behavior is that when the Hyperlink is clicked, GWT goes to the new place.  There is no notification to the activity.  I suppose a ClickHandler could be added, but it would have no usefulness other than testing.  Would it be better to not use a Hyperlink (maybe a Label with a click handler) and add a selectPerson() method to the Presenter.  Then the activity could test the selection and handle the advancing to the new Place?

2) I've seen a technique where a Display interface is created within the Presenter.  It contains a list of HasText and HasClickHandlers that conceptually define the View.  It seems pretty fine grained.   Of course, this has the same issue related to Hyperlinks. 

Anway, I'd appreciate feedback on various approaches.

Thomas Broyer

unread,
Jun 13, 2012, 6:30:26 AM6/13/12
to google-we...@googlegroups.com


On Wednesday, June 13, 2012 2:59:33 AM UTC+2, Mike Dee wrote:

A couple of questions regarding a test I'd like to perform.  Let's assume that testing occurs with a live database (and GWT-RPC).  I know that contest of the database and what should be returned for certain queries.  I'd like to run tests that execute certain queries and make sure they appear in the table (at certain positions).

I'd probably rather use Selenium/WebDriver tests here.
 
1) How would I test the clicking within the results.  For example, each person's name appears (per row) as a Hyperlink in the results.  This is implemented in a CellTable with Hyperlinks.  Normal behavior is that when the Hyperlink is clicked, GWT goes to the new place.  There is no notification to the activity.  I suppose a ClickHandler could be added, but it would have no usefulness other than testing.  Would it be better to not use a Hyperlink (maybe a Label with a click handler) and add a selectPerson() method to the Presenter.  Then the activity could test the selection and handle the advancing to the new Place?

How can you possibly use a Hyperlink (widget) in a CellTable?!
You might want to handle the "click" event from within your cell and call History.newItem() or PlaceController#goTo() from there (still not testable), or use a callback (similar to ButtonCell, ActionCell, or ClickableTextCell) where you put the History/PlaceController stuff, or route it through the Presenter.
 
2) I've seen a technique where a Display interface is created within the Presenter.  It contains a list of HasText and HasClickHandlers that conceptually define the View.  It seems pretty fine grained.   Of course, this has the same issue related to Hyperlinks.

Don't do that, if you ever write tests with a mock view, it'll be a pain to mock. 

Mike Dee

unread,
Jun 13, 2012, 5:48:22 PM6/13/12
to google-we...@googlegroups.com
I got this HyperlinkCell class somewhere and it works pretty good.

public class HyperlinkCell extends AbstractCell<Hyperlink>
{
@Override
public void render( com.google.gwt.cell.client.Cell.Context context, Hyperlink h, SafeHtmlBuilder sb )
{
sb.append( SafeHtmlUtils.fromTrustedString( h.toString() ) );
}
}

As I thought about it more I ask myself what do I want to test.  I'd like to test that the data in the view is retrieved properly, put into a query properly, and that the query works.  I don't need to test that the view displays things properly.  I'll assume Google tested its widgets.

Given that, what do you think about this idea for testing?  I got this idea by examining the Display interface technique.

Add getters and setters to for each view field.  Using the above example,

public class PersonSearchViewImpl extends ResizeComposite implements PersonSearchView
{
    public void setFirstname( String firstname )
    {
       this.firstname.setText( firstname );
    }

    public String getFirstname()
    {
       firstname.getText();
    }
}

Then a GWTTestCase could be written to call all the setters (which fill in the form fields) and press the Find button (need to add a method to simulate the pressing of the Find button).

Thomas Broyer

unread,
Jun 13, 2012, 7:54:26 PM6/13/12
to google-we...@googlegroups.com


On Wednesday, June 13, 2012 11:48:22 PM UTC+2, Mike Dee wrote:
I got this HyperlinkCell class somewhere and it works pretty good.

public class HyperlinkCell extends AbstractCell<Hyperlink>
{
@Override
public void render( com.google.gwt.cell.client.Cell.Context context, Hyperlink h, SafeHtmlBuilder sb )
{
sb.append( SafeHtmlUtils.fromTrustedString( h.toString() ) );
}
}

That's basically equivalent to this lighter-weight version (does not construct a DOM object for the Hyperlink widget):

public class LinkCell extends AbstractCell<TokenAndLabel> {
   interface Templates extends SafeHtmlTemplates {
      @Template("<a href='{0}'>{1}</a>")
      SafeHtml link(SafeUri token, String label);
   }

   private static final Templates TEMPLATE = GWT.create(Templates.class);
   
   @Override
   public voif render(Cell.Context context, TokenAndLabel value, SafeHtmlBuilder sb) {
      sb.append(TEMPLATE.template(UriUtils.fromString("#" + value.getToken()), value.getLabel()));
   }
}
 
As I thought about it more I ask myself what do I want to test.  I'd like to test that the data in the view is retrieved properly, put into a query properly, and that the query works.  I don't need to test that the view displays things properly.  I'll assume Google tested its widgets.

Given that, what do you think about this idea for testing?  I got this idea by examining the Display interface technique.

Add getters and setters to for each view field.  Using the above example,

public class PersonSearchViewImpl extends ResizeComposite implements PersonSearchView
{
    public void setFirstname( String firstname )
    {
       this.firstname.setText( firstname );
    }

    public String getFirstname()
    {
       firstname.getText();
    }
}

The Wave guys came up with a model where the presenter controls the view, so there's no getter; the view calls the presenter back with the values when needed (i.e. your find() method would have the firstname et al. as arguments).

Then a GWTTestCase could be written to call all the setters (which fill in the form fields) and press the Find button (need to add a method to simulate the pressing of the Find button).

If you assume Google tested the widgets, why are you using a GWTTestCase? Mock the view and use a standard JUnit test case, it'll run so much faster! 

Mike Dee

unread,
Jun 14, 2012, 12:19:23 AM6/14/12
to google-we...@googlegroups.com
The Wave guys came up with a model where the presenter controls the view, so there's no getter; the view calls the presenter back with the values when needed (i.e. your find() method would have the firstname et al. as arguments).

Useful, thanks.  I think we are doing something along those lines, but I'm not sure we'll stick with.  Still experimenting with testing.
 

Then a GWTTestCase could be written to call all the setters (which fill in the form fields) and press the Find button (need to add a method to simulate the pressing of the Find button).

If you assume Google tested the widgets, why are you using a GWTTestCase? Mock the view and use a standard JUnit test case, it'll run so much faster! 

Your question brings up an issue I struggle with.  I understand the benefits of MVP in terms of testing.  However, I want to test the back-end simultaneously, including GWT-RPC and data queries.  As a result, I think GWTTestCase is needed.  You may be correct, Selenium is the better way to do that.  I haven't tried Selenium with GWT apps (would be interested in hearing how well and easy it is to work with in terms of GWT).  I've heard it can be difficult, particularly with GWT apps.  

I'm considering if the technique described above could be an alternative to many Selenium tests.  It still allows testing in Java (even though it uses GWTTestCase).  As a Java programmer, that is very desirable.  Think of how easy it would be test a query driven case.  With Java/GWTTestCase the test case can drive the query (almost like scripting a view), exercise all the mechanics of the query (including the GWT-RPC), and easily check the results.  Essentially, all the layers of software are tested.  The only thing left out is the actual data entry by the user.

A good point made in the video you mentioned is Google follows a 70/20/10 rule.  70% of test are micro tests that run quickly and involve no network traffice.  10% are big tests, largely GWTTestCase tests.  I guess I'm looking at the latter.  These are tests that run by QA to verify the product.  Being based on GWTTestCase (and it's being slow) is not that big of a deal, if it works better than Selenium.

Thomas Broyer

unread,
Jun 14, 2012, 5:37:05 AM6/14/12
to google-we...@googlegroups.com


On Thursday, June 14, 2012 6:19:23 AM UTC+2, Mike Dee wrote:
The Wave guys came up with a model where the presenter controls the view, so there's no getter; the view calls the presenter back with the values when needed (i.e. your find() method would have the firstname et al. as arguments).

Useful, thanks.  I think we are doing something along those lines, but I'm not sure we'll stick with.  Still experimenting with testing.
 

Then a GWTTestCase could be written to call all the setters (which fill in the form fields) and press the Find button (need to add a method to simulate the pressing of the Find button).

If you assume Google tested the widgets, why are you using a GWTTestCase? Mock the view and use a standard JUnit test case, it'll run so much faster! 

Your question brings up an issue I struggle with.  I understand the benefits of MVP in terms of testing.  However, I want to test the back-end simultaneously, including GWT-RPC and data queries.  As a result, I think GWTTestCase is needed.

In that case yes, either GWTTestCase or Selenium/WebDriver tests.
But using GWTTestCase you can still create a mock view if it makes things easier for you (though I must admit I'd use a mock RPC too in this case, and testing "data queries" separately from GWT); otherwise I think you'd have better results with Selenium/WebDriver (see below though).
 
You may be correct, Selenium is the better way to do that.  I haven't tried Selenium with GWT apps (would be interested in hearing how well and easy it is to work with in terms of GWT).  I've heard it can be difficult, particularly with GWT apps.

I haven't used Selenium/WebDriver either.
The key is to use ensureDebugId on your widgets, and run your tests against an app compiled with the com.google.gwt.debug.Debug module (so that ensureDebugId is not a no-op).

Mike Dee

unread,
Jun 14, 2012, 5:06:30 PM6/14/12
to google-we...@googlegroups.com
We probably we go with Selenium.  Someone else (other than me) will evaluate that.

I look at MVP + JRE based testing/mocking and I have trouble seeing the value.  All of our algorithms and database interactions are pojos and we have tons of JUnit tests written (without MVP + JRE tests/mocks).  I ask myself if there is much value in doing MVP + JRE based tests/mocks when Selenium will exercise those same areas.  Why do double the work?  Maybe I don't understand something or need to get used to the idea.

Then I ask myself why do MVP if not doing the MVP+JRE tests/mocks?  Yes, it makes the code cleaner, separating business logic from display.  Is that enough?

Peter Donald

unread,
Jun 14, 2012, 6:01:24 PM6/14/12
to google-we...@googlegroups.com
On Fri, Jun 15, 2012 at 7:06 AM, Mike Dee <mdichi...@gmail.com> wrote:
> I look at MVP + JRE based testing/mocking and I have trouble seeing the
> value.  All of our algorithms and database interactions are pojos and we
> have tons of JUnit tests written (without MVP + JRE tests/mocks).  I ask
> myself if there is much value in doing MVP + JRE based tests/mocks when
> Selenium will exercise those same areas.  Why do double the work?  Maybe I
> don't understand something or need to get used to the idea.

Selenium tests are slow to execute and expensive to write. MVP+mock
tests are relatively fast to write and fast to execute but may not
exercise the full stack. It sort of comes dow to when do you use
integration tests and when do you use unit tests.

So it is usually to expensive to get a good level of coverage using
Selenium alone. We try get good coverage using MVP+mock testig and
then reserve selenium tests to exercise a smaller number "high value"
scenarios


--
Cheers,

Peter Donald

Joseph Lust

unread,
Jun 16, 2012, 1:14:14 PM6/16/12
to google-we...@googlegroups.com
To add to Peter's comment, the JUnit/GWTTestCase coverage is still valuable, despite Selenium, because it allows fast identification of errors at an easily identifiable granularity.

For example, on a current GWT project with 1K classes, if a UI automation test failed with Selenium, then we know there is a problem, but it could be at many layers of our stack. Further, running our 2K Selenium (we use Cucumber, but very similar) tests takes two hours. Our developers are not going to run this before they checkin. But, our 3K JUnit/GWTTestCase tests take only 8 minutes to run. This way we can use features like TeamCity's Pretested Commit and run these tests before allowing a commit, and if a failure happens, it's been isolated to the class and method level for quick fixing.

Also note, you can easily test the RPC calls from GWTTestCase, see Asyncronous GWTTestCase.


Sincerely,
Joseph

Ed

unread,
Jun 19, 2012, 5:24:25 PM6/19/12
to google-we...@googlegroups.com
@Mike: also have a look at this topic, it might give you some idea's:
Reply all
Reply to author
Forward
0 new messages