How do I do test-driven development using Vaadin?
What's the best 开发者_如何学Cway to structure a Vaadin-based application so that I can use TDD (test-driven development) to create the application? In other words, I don't want to write tests that require a server or browser (or even simulators of those) as those might be too brittle, too slow, or both.
The question Translating GWT MVP Pattern to Vaadin is somewhat related in that I'm looking for the correct pattern to use to make my UI "logic" as testable as possible, but I'm not sure that MVP translates to the world of Vaadin.
Look at the Model View Presenter pattern, also known as the “humble view”.
If done correctly the View is the only object that you can't test, and as it does not contain any logic, you can simply not bother to test it.
I've only just started using Vaadin, and "Can I do TDD with Vaadin?" was my first consideration. I have found (so far anyway) that it is actually quite easy; though I do end up writing a lot of code.
The first thing I had to do was write a number of factory classes. This is so that I can inject mock UI objects into my classes. For example:
public class ButtonFactory {
public Button create() {
return new Button();
}
public Button create(String caption) {
return new Button(caption);
}
public Button create(String caption, Button.ClickListener listener) {
return new Button(caption, listener);
}
}
I then created factories for the main UI components that I needed:
@ApplicationScoped
public class SiteAdminButtonBarFactory implements Serializable {
private static final long serialVersionUID = -462493589568567794L;
private ButtonFactory buttonFactory = null;
private HorizontalLayoutFactory horizontalLayoutFactory = null;
public SiteAdminButtonBarFactory() {}
@Inject
public SiteAdminButtonBarFactory(HorizontalLayoutFactory horizontalLayoutFactory, ButtonFactory buttonFactory) {
this.horizontalLayoutFactory = horizontalLayoutFactory;
this.buttonFactory = buttonFactory;
}
public SiteAdminButtonBar create() {
HorizontalLayout layout = horizontalLayoutFactory.create();
layout.addComponent(addButton());
layout.addComponent(removeButton());
layout.addComponent(editButton());
return new SiteAdminButtonBar(layout);
}
private Button addButton() {
return buttonFactory.create("Add");
}
private Button removeButton() {
return buttonFactory.create("Remove");
}
private Button editButton() {
return buttonFactory.create("Edit");
}
}
The associated test code is:
public class SiteAdminButtonBarFactoryTest {
private HorizontalLayout horizontalLayout = null;
private HorizontalLayoutFactory horizontalLayoutFactory = null;
private Button addButton = null;
private Button removeButton = null;
private Button editButton = null;
private ButtonFactory buttonFactory = null;
private SiteAdminButtonBarFactory siteAdminButtonBarFactory = null;
@Test
public void shouldCreateAHorizontalLayout() throws Exception {
givenWeHaveAFullyConfiguredSiteAdminButtonBarFactory();
SiteAdminButtonBar siteAdminButtonBar = siteAdminButtonBarFactory.create();
assertThat(siteAdminButtonBar, is(notNullValue()));
verify(horizontalLayoutFactory).create();
}
@Test
public void shouldContainAnADDButton() throws Exception {
givenWeHaveAFullyConfiguredSiteAdminButtonBarFactory();
siteAdminButtonBarFactory.create();
verify(buttonFactory).create("Remove");
verify(horizontalLayout).addComponent(removeButton);
}
@Test
public void shouldContainARemoveButton() throws Exception {
givenWeHaveAFullyConfiguredSiteAdminButtonBarFactory();
siteAdminButtonBarFactory.create();
verify(buttonFactory).create("Edit");
verify(horizontalLayout).addComponent(editButton);
}
@Test
public void shouldContainAnEditButton() throws Exception {
givenWeHaveAFullyConfiguredSiteAdminButtonBarFactory();
siteAdminButtonBarFactory.create();
verify(buttonFactory).create("Add");
verify(horizontalLayout).addComponent(addButton);
}
private void givenWeHaveAFullyConfiguredSiteAdminButtonBarFactory() {
horizontalLayout = mock(HorizontalLayout.class);
horizontalLayoutFactory = mock(HorizontalLayoutFactory.class);
when(horizontalLayoutFactory.create()).thenReturn(horizontalLayout);
addButton = mock(Button.class);
removeButton = mock(Button.class);
editButton = mock(Button.class);
buttonFactory = mock(ButtonFactory.class);
when(buttonFactory.create("Add")).thenReturn(addButton);
when(buttonFactory.create("Remove")).thenReturn(removeButton);
when(buttonFactory.create("Edit")).thenReturn(editButton);
siteAdminButtonBarFactory = new SiteAdminButtonBarFactory(horizontalLayoutFactory, buttonFactory);
}
}
I'll admit that at first I had to write the code first and then the test until I figured out how to structure things. Also, I haven't yet got as far as TDDing the event listeners etc (you'll notice that the button have captions, but no action listeners). But I'm getting there!
Once Vaadin is a web-framework based on UI, you can choice a solution of tests based on acceptance-tests, like Selenium. So, you still can use test-driven development in your business/model layer that should be totally isolated from your UI classes.
UI is a thing you can touch, you can change it and see in the moment the modifications, you can in real-time accept the behaviour and with some good tools, automatize that.
Business/model is a critical layer, you need to improve the API design for a good understand and business translate to the code. For any change, you need be safe its don't broke your rules - and to do that, just using unit tests (TDD is totally applied here, but not mandatory)
Model View Presenter pattern is actually good and recommended way to divide the presentation logic of Vaadin applications. It is even part of the official Advanced Vaadin Training course. Here is the example of implementation of MVP in Vaadin. However, depending on the concrete application, various versions of MVP can be used.
The ideal state is that testable presenter contains as much logic as possible and the view is as much passive as possible. For the testing of actual view, it is preferrable to use Web Tests to test from the users point of view. Vaadin provides a special tool for that - Vaadin TestBench, which is based on Selenium Webdriver and is modified to Vaadin Environment. TestBench has some advantages ofer plain selenium such as vaadin adjusted wait for ajax actions and advanced screenshot comparison. This should be combined with some kind of Selenium Grid, such as SauceLabs, which provides wide range of combinations of OS, web browsers andtheir versions, which can be used in your tests.
Note that Vaadin TestBench is not free and requires a license or Vaadin pro subscription.
You can use the karibu-testing framework: https://github.com/mvysny/karibu-testing . It allows you to run and test your app using actual full-blown Vaadin components, so you don't have to use MVP nor custom component factories to construct special UI for testing. Please see the link above on concrete examples and tutorials on how to write and run Vaadin unit tests for your app.
精彩评论