KEMBAR78
Test Driven Development with JavaFX | PDF
Test-Driven 
Development with 
JavaFX
About us 
Sven Ruppert Hendrik Ebbers 
@hendrikEbbers 
www.guigarage.com 
@SvenRuppert 
www.rapidpm.org
Content 
• Testing 
• Testing 
• Testing 
Basics 
Frameworks 
CDI
Testing an application 
• unit tests 
how to test an UI component 
• integration tests 
• system tests 
how to test an UI workflow
manual testing 
• a tester tests the complete app 
• create a test plan 
• update the test plan for each 
release 
• test each release
CI / CD 
• update the test plan for each commit 
• test each commit 
we don’t want this
UI Test Tools & Libs
IDE based 
Tools like Selenium 
• QF-Test 
• commercial product 
• developer licence costs around 1995 € 
• no JUnit approach 
• CI integration 
• nearly the same as froglogic... 
Oct 2014
JemmyFX 
• is for JavaFX 2.2 
• last commit is over 2 years 
ago 
• looks like there is no 
development activity
Automaton 
• is for JavaFX2 
• is developed for Java7 (> u55), is 
running until Java8u11 
• written in Groovy 
• could test Swing and JavaFX 2 
• recommend TestFX for JavaFX 
see homepage
MarvinFX 
• https://github.com/guigarage/MarvinFX 
• Provides Supervisors for JavaFX Properties
MarvinFX 
define 
PropertySupervisor<String> textSupervisor = 
new PropertySupervisor<>(textfield.textProperty()); 
rules 
textPropertySupervisor.assertWillChange(); 
textPropertySupervisor.assertWillChangeByDefinedCount(3); 
textPropertySupervisor.assertWillChangeThisWay("A", "B", "C"); 
interaction 
//interact with UI by using TestFX 
check 
textPropertySupervisor.confirm();
TestFX
TestFX 
• active development 
• LTS branch for Java7 is 
available 
• active branch JavaFX8 only
TestFX 
• verifying the behavior of JavaFX 
applications 
• API for interacting with JavaFX applications. 
• fluent and clean API 
• Supports Hamcrest Matchers and Lambda 
expressions. 
• Screenshots of failed tests.
TestFX Deep Dive
Let’s start with a small app
Pseudo Code 
click(".text-field").type("steve"); 
click(".password-field").type("duke4ever"); 
click(".button:default"); 
assertNodeExists( ".dialog" );
step by step 
• Each test must extend the GuiTest class 
public class MyTest extends GuiTest { 
! 
@Test 
public void testLogin() { . . . } 
! 
}
step by step 
• Provide the root node in your GuiTest 
class 
public class MyTest extends GuiTest { 
! 
protected Parent getRootNode() { 
. . . 
} 
}
step by step 
• The GuiTest class provides a lot of 
functions that can be used to interact with 
JavaFX 
@Test 
public void testLogin() { 
click(„.text-field“); 
type("steve"); 
// . . . 
}
step by step 
• You can use the fluent API 
• You can use CSS selectors to find components 
@Test 
public void testLogin() { 
click(„#text-field“).type(„steve“); 
// . . . 
}
How to interact 
with a specific node?
Extended Node Search 
• TestFX provides additional search methods 
find(„#name-textfield“, find(„#edit-panel“)) 
find the textfield in the subpanel
Demo
View Objects Pattern
Any Idea what 
this test does? 
click("#user-field").type("steve"); 
click("#password-field").type("duke4ever"); 
click("#login-button"); 
click("#menu-button"); 
click("#action-35"); 
click("#tab-5"); 
click("#next"); 
click("#next"); 
click("#next"); 
click("#details"); 
assertNodeExists( "#user-picture" );
View Objects Pattern
View Objects Pattern 
• define a class / object for each view in 
your application 
SearchViewObject AlbumsViewObject 
TracksViewObject PlayViewObject
structure 
• Each user interaction is defined as a method 
• The class provides methods to check important 
states 
public class AlbumsViewObject { 
openAlbum(String name) {} 
checkAlbumCount(int count) {} 
assertContainsAlbum(String name) {} 
}
structure 
• Each method returns the view object for the page 
that is visible after the method has been executed 
• If the view won’t change by calling a method the 
method will return “this“ 
public TracksViewObject openAlbum(String name) { 
click((Text t) -> t.getText().contains(name)); 
return new TracksViewObject(getTestHandler()); 
} 
public AlbumsViewObject checkAlbumCount(int count) { 
assertEquals(count, getList().size()); 
return this; 
}
write readable tests 
@Test 
public void checkTrackCount() { 
new SearchView(this). 
search("Rise Against“). 
openAlbum("The Black Market“). 
checkTrackCountOfSelectedAlbum(12); 
}
Demo
Testing DataFX Flow 
public class Tests extends FlowTest { 
! 
protected Class<?> getFlowStartController() { 
return SearchController.class; 
} 
! 
@Test 
public void testSearch() { 
click(„#searchfield“) . . . 
} 
}
Injection
Problem 
@FlowScoped 
public class ITunesDataModel { 
! 
public void search(String artist) { 
//REST call 
} 
! 
}
extend the class 
@FlowScoped 
public class TestDataModel extends ITunesDataModel 
{ 
! 
public void search(String artist) { 
getAlbums().add(. . .); 
//Adding test data 
} 
! 
}
Solution for DataFX 
public class Tests extends FlowTest { 
! 
@Override 
protected void injectTestData(Injector injector) { 
injector.inject(new TestDataModel(), 
ITunesDataModel.class); 
} 
! 
}
Demo
Testing Afterburner.fx 
• apache licensed 
as lean as possible: 3 classes, no 
external dependencies 
• combines: FXML, Convention over 
Configuration and JSR-330 / @Inject 
• integrated with maven 3
Testing Afterburner.fx 
• under active development 
• injection over a few steps is working 
• postconstruct is working 
• using existing CDI Services with 
Annotations (Scopes and so on) is not 
working with afterburner 
• no mixed mode with CDI and afterburner.fx
Testing Afterburner.fx 
• TestFX is working fine with 
afterburner.fx 
• Definition of the tests are the 
same as without afterburner.fx
Demo Afterburner.fx
TestFX & CDI
Why CDI? 
• Because we want to use 
Mocks 
• Dynamic reconfiguration
Example 
Pa n e 
Controller 
Service
Example 
Pa n e 
Controller 
Service
Example 
Service myService = new Service(); 
@Inject Service myService;
Plain FX Demo
Example 
Pa n e 
Controller 
Mock 
Mock 
Mock 
Service
Example 
Pa n e 
Controller 
Mock 
Mock 
Service
Example 
Pa n e 
Controller 
Mock 
Service
CDI Basic Pattern 
• The production source must 
not contain test sources 
• Therefore we need to 
decouple test & production 
sources 
physically!
CDI Basic Pattern 
@Inject @MyQualifier 
ServiceInterface myService; 
creates 
@Producer @MyQualifier 
ServiceInterface createService(){. . .}
CDI Basic Pattern 
@Inject @MyQualifier 
ServiceInterface myService; 
@Producer @MyQualifier 
ServiceInterface createService(){. . .} 
Mock
CDI Basic Pattern 
if(„production“) { 
return service; 
} else { 
return mock; 
}
CDI Basic Pattern 
@Inject @MyQualifier 
ServiceInterface myService; 
@Producer @MyQualifier 
ServiceInterface createService(){. . .} 
Mock ?
CDI Basic Pattern 
@Inject @MyQualifier 
ServiceInterface myService; 
@Producer @MyQualifier 
ServiceInterface createService(){. . .} 
@Producer @Prod 
ServiceInterface create(){…} 
Mock 
@Producer @Mock 
ServiceInterface create(){…} 
src/main/java src/test/java
CDI Basic Pattern
CDI Basic Pattern
CDI Basic Pattern
CDI Demo
Where to start? 
• www.rapidpm.org 
• github.com/svenruppert/ 
javaone2014
QA

Test Driven Development with JavaFX

  • 1.
  • 2.
    About us SvenRuppert Hendrik Ebbers @hendrikEbbers www.guigarage.com @SvenRuppert www.rapidpm.org
  • 3.
    Content • Testing • Testing • Testing Basics Frameworks CDI
  • 4.
    Testing an application • unit tests how to test an UI component • integration tests • system tests how to test an UI workflow
  • 5.
    manual testing •a tester tests the complete app • create a test plan • update the test plan for each release • test each release
  • 6.
    CI / CD • update the test plan for each commit • test each commit we don’t want this
  • 7.
  • 8.
    IDE based Toolslike Selenium • QF-Test • commercial product • developer licence costs around 1995 € • no JUnit approach • CI integration • nearly the same as froglogic... Oct 2014
  • 9.
    JemmyFX • isfor JavaFX 2.2 • last commit is over 2 years ago • looks like there is no development activity
  • 10.
    Automaton • isfor JavaFX2 • is developed for Java7 (> u55), is running until Java8u11 • written in Groovy • could test Swing and JavaFX 2 • recommend TestFX for JavaFX see homepage
  • 11.
    MarvinFX • https://github.com/guigarage/MarvinFX • Provides Supervisors for JavaFX Properties
  • 12.
    MarvinFX define PropertySupervisor<String>textSupervisor = new PropertySupervisor<>(textfield.textProperty()); rules textPropertySupervisor.assertWillChange(); textPropertySupervisor.assertWillChangeByDefinedCount(3); textPropertySupervisor.assertWillChangeThisWay("A", "B", "C"); interaction //interact with UI by using TestFX check textPropertySupervisor.confirm();
  • 13.
  • 14.
    TestFX • activedevelopment • LTS branch for Java7 is available • active branch JavaFX8 only
  • 15.
    TestFX • verifyingthe behavior of JavaFX applications • API for interacting with JavaFX applications. • fluent and clean API • Supports Hamcrest Matchers and Lambda expressions. • Screenshots of failed tests.
  • 16.
  • 17.
    Let’s start witha small app
  • 18.
    Pseudo Code click(".text-field").type("steve"); click(".password-field").type("duke4ever"); click(".button:default"); assertNodeExists( ".dialog" );
  • 19.
    step by step • Each test must extend the GuiTest class public class MyTest extends GuiTest { ! @Test public void testLogin() { . . . } ! }
  • 20.
    step by step • Provide the root node in your GuiTest class public class MyTest extends GuiTest { ! protected Parent getRootNode() { . . . } }
  • 21.
    step by step • The GuiTest class provides a lot of functions that can be used to interact with JavaFX @Test public void testLogin() { click(„.text-field“); type("steve"); // . . . }
  • 22.
    step by step • You can use the fluent API • You can use CSS selectors to find components @Test public void testLogin() { click(„#text-field“).type(„steve“); // . . . }
  • 23.
    How to interact with a specific node?
  • 24.
    Extended Node Search • TestFX provides additional search methods find(„#name-textfield“, find(„#edit-panel“)) find the textfield in the subpanel
  • 25.
  • 26.
  • 27.
    Any Idea what this test does? click("#user-field").type("steve"); click("#password-field").type("duke4ever"); click("#login-button"); click("#menu-button"); click("#action-35"); click("#tab-5"); click("#next"); click("#next"); click("#next"); click("#details"); assertNodeExists( "#user-picture" );
  • 28.
  • 29.
    View Objects Pattern • define a class / object for each view in your application SearchViewObject AlbumsViewObject TracksViewObject PlayViewObject
  • 30.
    structure • Eachuser interaction is defined as a method • The class provides methods to check important states public class AlbumsViewObject { openAlbum(String name) {} checkAlbumCount(int count) {} assertContainsAlbum(String name) {} }
  • 31.
    structure • Eachmethod returns the view object for the page that is visible after the method has been executed • If the view won’t change by calling a method the method will return “this“ public TracksViewObject openAlbum(String name) { click((Text t) -> t.getText().contains(name)); return new TracksViewObject(getTestHandler()); } public AlbumsViewObject checkAlbumCount(int count) { assertEquals(count, getList().size()); return this; }
  • 32.
    write readable tests @Test public void checkTrackCount() { new SearchView(this). search("Rise Against“). openAlbum("The Black Market“). checkTrackCountOfSelectedAlbum(12); }
  • 33.
  • 34.
    Testing DataFX Flow public class Tests extends FlowTest { ! protected Class<?> getFlowStartController() { return SearchController.class; } ! @Test public void testSearch() { click(„#searchfield“) . . . } }
  • 35.
  • 36.
    Problem @FlowScoped publicclass ITunesDataModel { ! public void search(String artist) { //REST call } ! }
  • 37.
    extend the class @FlowScoped public class TestDataModel extends ITunesDataModel { ! public void search(String artist) { getAlbums().add(. . .); //Adding test data } ! }
  • 38.
    Solution for DataFX public class Tests extends FlowTest { ! @Override protected void injectTestData(Injector injector) { injector.inject(new TestDataModel(), ITunesDataModel.class); } ! }
  • 39.
  • 40.
    Testing Afterburner.fx •apache licensed as lean as possible: 3 classes, no external dependencies • combines: FXML, Convention over Configuration and JSR-330 / @Inject • integrated with maven 3
  • 41.
    Testing Afterburner.fx •under active development • injection over a few steps is working • postconstruct is working • using existing CDI Services with Annotations (Scopes and so on) is not working with afterburner • no mixed mode with CDI and afterburner.fx
  • 42.
    Testing Afterburner.fx •TestFX is working fine with afterburner.fx • Definition of the tests are the same as without afterburner.fx
  • 43.
  • 44.
  • 45.
    Why CDI? •Because we want to use Mocks • Dynamic reconfiguration
  • 46.
    Example Pa ne Controller Service
  • 47.
    Example Pa ne Controller Service
  • 48.
    Example Service myService= new Service(); @Inject Service myService;
  • 49.
  • 50.
    Example Pa ne Controller Mock Mock Mock Service
  • 51.
    Example Pa ne Controller Mock Mock Service
  • 52.
    Example Pa ne Controller Mock Service
  • 53.
    CDI Basic Pattern • The production source must not contain test sources • Therefore we need to decouple test & production sources physically!
  • 54.
    CDI Basic Pattern @Inject @MyQualifier ServiceInterface myService; creates @Producer @MyQualifier ServiceInterface createService(){. . .}
  • 55.
    CDI Basic Pattern @Inject @MyQualifier ServiceInterface myService; @Producer @MyQualifier ServiceInterface createService(){. . .} Mock
  • 56.
    CDI Basic Pattern if(„production“) { return service; } else { return mock; }
  • 57.
    CDI Basic Pattern @Inject @MyQualifier ServiceInterface myService; @Producer @MyQualifier ServiceInterface createService(){. . .} Mock ?
  • 58.
    CDI Basic Pattern @Inject @MyQualifier ServiceInterface myService; @Producer @MyQualifier ServiceInterface createService(){. . .} @Producer @Prod ServiceInterface create(){…} Mock @Producer @Mock ServiceInterface create(){…} src/main/java src/test/java
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
    Where to start? • www.rapidpm.org • github.com/svenruppert/ javaone2014
  • 64.