SlideShare a Scribd company logo
TDD with AEM
Jan Wloka
jan.wloka@quatico.com
Quatico Solutions AG
Text Image Component
Text Image Component
JSP Template
class
TextImageItem
class
TextImageController
<creates>
<creates>
<uses>
<uses>
Text Image Component
MVC
JSP Template
class
TextImageItem
class
TextImageController
<creates>
<creates>
<uses>
<uses>
Client
Server
Author
Deploy Create
Running the Component
JSP Template
class
TextImageItem
class
TextImageController
Client
Server
Author
Deploy Create
Running the Component
Round trip: Single feature ~1min
JSP Template
class
TextImageItem
class
TextImageController
Testing Text Image Component
AEM
Resources
App
TextImage
TextImage
Test
<creates>
<tests>
AEM Sling Testing API
@Before
public void setUp() throws Exception {
URL url = new URL(serverUrl);
httpClient = new HttpClient();
httpClient.getParams().setAuthenticationPreemptive(true);
httpClient.getState().setCredentials(new AuthScope(url.getHost(), url.getPort(), ANY_REALM), getCredentials());
client = new SlingIntegrationTestClient(httpClient);
}
private void createPage(String path, Properties properties, Template template) throws Exception {
String[] pair = splitPathName(path);
HttpMethod createReq = new PostMethod(serverUrl + "/bin/wcmcommand");
createReq.setQueryString(new Properties().append("cmd", “createPage").append("title", pair[1])
.append("parentPath", pair[0]).append(TEMPLATE, template.getName()).toPairs());
if (HttpStatus.SC_OK != httpClient.executeMethod(createReq)) {
throw new HttpResponseException(createReq.getStatusCode(), createReq.getStatusText());
}
if (!properties.isEmpty()) {
HttpMethod changeReq = new PostMethod(serverUrl + path + "/" + JCR_CONTENT);
changeReq.setQueryString(properties.toPairs(true));
if (HttpStatus.SC_OK != httpClient.executeMethod(changeReq)) {
throw new HttpResponseException(changeReq.getStatusCode(), changeReq.getStatusText());
}
}
}
private String createResource(String path, Properties properties) throws IOException {
String nodeUrl = serverUrl + path;
client.createNode(nodeUrl, properties.<String>toMap());
return nodeUrl;
}
AEM Sling Testing API
@Before
public void setUp() throws Exception {
URL url = new URL(serverUrl);
httpClient = new HttpClient();
httpClient.getParams().setAuthenticationPreemptive(true);
httpClient.getState().setCredentials(new AuthScope(url.getHost(), url.getPort(), ANY_REALM), getCredentials());
client = new SlingIntegrationTestClient(httpClient);
}
private void createPage(String path, Properties properties, Template template) throws Exception {
String[] pair = splitPathName(path);
HttpMethod createReq = new PostMethod(serverUrl + "/bin/wcmcommand");
createReq.setQueryString(new Properties().append("cmd", “createPage").append("title", pair[1])
.append("parentPath", pair[0]).append(TEMPLATE, template.getName()).toPairs());
if (HttpStatus.SC_OK != httpClient.executeMethod(createReq)) {
throw new HttpResponseException(createReq.getStatusCode(), createReq.getStatusText());
}
if (!properties.isEmpty()) {
HttpMethod changeReq = new PostMethod(serverUrl + path + "/" + JCR_CONTENT);
changeReq.setQueryString(properties.toPairs(true));
if (HttpStatus.SC_OK != httpClient.executeMethod(changeReq)) {
throw new HttpResponseException(changeReq.getStatusCode(), changeReq.getStatusText());
}
}
}
private String createResource(String path, Properties properties) throws IOException {
String nodeUrl = serverUrl + path;
client.createNode(nodeUrl, properties.<String>toMap());
return nodeUrl;
}
Setup Client
AEM Sling Testing API
@Before
public void setUp() throws Exception {
URL url = new URL(serverUrl);
httpClient = new HttpClient();
httpClient.getParams().setAuthenticationPreemptive(true);
httpClient.getState().setCredentials(new AuthScope(url.getHost(), url.getPort(), ANY_REALM), getCredentials());
client = new SlingIntegrationTestClient(httpClient);
}
private void createPage(String path, Properties properties, Template template) throws Exception {
String[] pair = splitPathName(path);
HttpMethod createReq = new PostMethod(serverUrl + "/bin/wcmcommand");
createReq.setQueryString(new Properties().append("cmd", “createPage").append("title", pair[1])
.append("parentPath", pair[0]).append(TEMPLATE, template.getName()).toPairs());
if (HttpStatus.SC_OK != httpClient.executeMethod(createReq)) {
throw new HttpResponseException(createReq.getStatusCode(), createReq.getStatusText());
}
if (!properties.isEmpty()) {
HttpMethod changeReq = new PostMethod(serverUrl + path + "/" + JCR_CONTENT);
changeReq.setQueryString(properties.toPairs(true));
if (HttpStatus.SC_OK != httpClient.executeMethod(changeReq)) {
throw new HttpResponseException(changeReq.getStatusCode(), changeReq.getStatusText());
}
}
}
private String createResource(String path, Properties properties) throws IOException {
String nodeUrl = serverUrl + path;
client.createNode(nodeUrl, properties.<String>toMap());
return nodeUrl;
}
Create
Page
AEM Sling Testing API
@Before
public void setUp() throws Exception {
URL url = new URL(serverUrl);
httpClient = new HttpClient();
httpClient.getParams().setAuthenticationPreemptive(true);
httpClient.getState().setCredentials(new AuthScope(url.getHost(), url.getPort(), ANY_REALM), getCredentials());
client = new SlingIntegrationTestClient(httpClient);
}
private void createPage(String path, Properties properties, Template template) throws Exception {
String[] pair = splitPathName(path);
HttpMethod createReq = new PostMethod(serverUrl + "/bin/wcmcommand");
createReq.setQueryString(new Properties().append("cmd", “createPage").append("title", pair[1])
.append("parentPath", pair[0]).append(TEMPLATE, template.getName()).toPairs());
if (HttpStatus.SC_OK != httpClient.executeMethod(createReq)) {
throw new HttpResponseException(createReq.getStatusCode(), createReq.getStatusText());
}
if (!properties.isEmpty()) {
HttpMethod changeReq = new PostMethod(serverUrl + path + "/" + JCR_CONTENT);
changeReq.setQueryString(properties.toPairs(true));
if (HttpStatus.SC_OK != httpClient.executeMethod(changeReq)) {
throw new HttpResponseException(changeReq.getStatusCode(), changeReq.getStatusText());
}
}
}
private String createResource(String path, Properties properties) throws IOException {
String nodeUrl = serverUrl + path;
client.createNode(nodeUrl, properties.<String>toMap());
return nodeUrl;
}
Setup Resource
Test: Image caption is shown
@Test
public void testGetImageLegendWithSourceAndCaptionReturnsBoth() throws Exception {
ResourceResolver resolver = new ResourceResolverImpl(client);
createPage(“/content/foobar”, new Properties(), PageType.PRESENCE.getTemplate());
createPage(“/content/foobar/ko”, new Properties(), PageType.LANGUAGE.getTemplate());
createPage(“/content/foobar/ko/home", new Properties(), PageType.HOME.getTemplate());
createPage(“/content/foobar/ko/home/content", new Properties(), PageType.DETAIL.getTemplate());
Resource contentPage = resolver.getResource(“/content/foobar/ko/home/content");
createResource(contentPage.getPath() + "/jcr:content/textimage", new Properties());
Resource target = resolver.getResource(contentPage.getPath() + "/jcr:content/textimage");
Properties imageProps = new Properties()
.append("source", "<sourceValue>")
.append("caption", "titleValue")
.append("sling:resourceType", ComponentType.IMAGE)
.append(JCR_LASTMODIFIED, new Time().format(DateFormat.GMT))
.append(JCR_LAST_MODIFIED_BY, "admin");
createResource(contentPage.getPath() + "/jcr:content/textimage/image", imageProps);
TextImageController testObj = new TextImageController().setup(createContext(target, contentPage));
assertEquals("titleValue<br />&lt;sourceValue&gt;", testObj.getImageLegend());
}
Test: Image caption is shown
@Test
public void testGetImageLegendWithSourceAndCaptionReturnsBoth() throws Exception {
ResourceResolver resolver = new ResourceResolverImpl(client);
createPage(“/content/foobar”, new Properties(), PageType.PRESENCE.getTemplate());
createPage(“/content/foobar/ko”, new Properties(), PageType.LANGUAGE.getTemplate());
createPage(“/content/foobar/ko/home", new Properties(), PageType.HOME.getTemplate());
createPage(“/content/foobar/ko/home/content", new Properties(), PageType.DETAIL.getTemplate());
Resource contentPage = resolver.getResource(“/content/foobar/ko/home/content");
createResource(contentPage.getPath() + "/jcr:content/textimage", new Properties());
Resource target = resolver.getResource(contentPage.getPath() + "/jcr:content/textimage");
Properties imageProps = new Properties()
.append("source", "<sourceValue>")
.append("caption", "titleValue")
.append("sling:resourceType", ComponentType.IMAGE)
.append(JCR_LASTMODIFIED, new Time().format(DateFormat.GMT))
.append(JCR_LAST_MODIFIED_BY, "admin");
createResource(contentPage.getPath() + "/jcr:content/textimage/image", imageProps);
TextImageController testObj = new TextImageController().setup(createContext(target, contentPage));
assertEquals("titleValue<br />&lt;sourceValue&gt;", testObj.getImageLegend());
}
Assemble
Test: Image caption is shown
@Test
public void testGetImageLegendWithSourceAndCaptionReturnsBoth() throws Exception {
ResourceResolver resolver = new ResourceResolverImpl(client);
createPage(“/content/foobar”, new Properties(), PageType.PRESENCE.getTemplate());
createPage(“/content/foobar/ko”, new Properties(), PageType.LANGUAGE.getTemplate());
createPage(“/content/foobar/ko/home", new Properties(), PageType.HOME.getTemplate());
createPage(“/content/foobar/ko/home/content", new Properties(), PageType.DETAIL.getTemplate());
Resource contentPage = resolver.getResource(“/content/foobar/ko/home/content");
createResource(contentPage.getPath() + "/jcr:content/textimage", new Properties());
Resource target = resolver.getResource(contentPage.getPath() + "/jcr:content/textimage");
Properties imageProps = new Properties()
.append("source", "<sourceValue>")
.append("caption", "titleValue")
.append("sling:resourceType", ComponentType.IMAGE)
.append(JCR_LASTMODIFIED, new Time().format(DateFormat.GMT))
.append(JCR_LAST_MODIFIED_BY, "admin");
createResource(contentPage.getPath() + "/jcr:content/textimage/image", imageProps);
TextImageController testObj = new TextImageController().setup(createContext(target, contentPage));
assertEquals("titleValue<br />&lt;sourceValue&gt;", testObj.getImageLegend());
}
Assemble
Act
Test: Image caption is shown
@Test
public void testGetImageLegendWithSourceAndCaptionReturnsBoth() throws Exception {
ResourceResolver resolver = new ResourceResolverImpl(client);
createPage(“/content/foobar”, new Properties(), PageType.PRESENCE.getTemplate());
createPage(“/content/foobar/ko”, new Properties(), PageType.LANGUAGE.getTemplate());
createPage(“/content/foobar/ko/home", new Properties(), PageType.HOME.getTemplate());
createPage(“/content/foobar/ko/home/content", new Properties(), PageType.DETAIL.getTemplate());
Resource contentPage = resolver.getResource(“/content/foobar/ko/home/content");
createResource(contentPage.getPath() + "/jcr:content/textimage", new Properties());
Resource target = resolver.getResource(contentPage.getPath() + "/jcr:content/textimage");
Properties imageProps = new Properties()
.append("source", "<sourceValue>")
.append("caption", "titleValue")
.append("sling:resourceType", ComponentType.IMAGE)
.append(JCR_LASTMODIFIED, new Time().format(DateFormat.GMT))
.append(JCR_LAST_MODIFIED_BY, "admin");
createResource(contentPage.getPath() + "/jcr:content/textimage/image", imageProps);
TextImageController testObj = new TextImageController().setup(createContext(target, contentPage));
assertEquals("titleValue<br />&lt;sourceValue&gt;", testObj.getImageLegend());
}
Assemble
Act
Assert
Integrated Tests with AEM
Author
Publish
boot2
JCR
JSP
JS/CSS
foo() : Foo
bar() : Bar
zip: Zip
zap : Zap
TextImage
testFooWithNull()
testFooWithOk()
testBarWithEmpty()
testBarWithResult()
TextImageTest
Request
Response
Client
Server
Integrated Tests with AEM
Round trip: ~80 tested features ~2m30s
Author
Publish
boot2
JCR
JSP
JS/CSS
foo() : Foo
bar() : Bar
zip: Zip
zap : Zap
TextImage
testFooWithNull()
testFooWithOk()
testBarWithEmpty()
testBarWithResult()
TextImageTest
Request
Response
Client
Server
It works, but still hurts!
Client Server
AEM
App
Test
Test
Test
Test
Test
It works, but still hurts!
Client Server
AEM
App
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
TestTest
Test
Test
Test
Test
Test
Test
Test
Test
TestTest
Test
Test
Test
Test
Test
Test
Test
Test
TestTest
Test
Test
Test
Test
Test
Test
Test
TestTest
Test
Test
Test
Test
Test
Test
Test
Test
It works, but still hurts!
Client Server
AEM
App
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
Test
TestTest
Test
Test
Test
Test
Test
Test
Test
Test
TestTest
Test
Test
Test
Test
Test
Test
Test
Test
TestTest
Test
Test
Test
Test
Test
Test
Test
TestTest
Test
Test
Test
Test
Test
Test
Test
Test
Delay (or prevent) feedback
Hard to write
Difficult to maintain
Flaky results
Low coverage
A unified testing API
AEM
App
App
Test API
A unified testing API
Test
API
AEM
Resources
App
TextImage
TextImage
Test <uses>
<tests>
API for creating Pages
class Pages {
aPresencePage(String, Object…)
aLanguagePage(String, Object…)
aHomePage(String, Object…)
aNewsPage(String, Object…)
aRssFeedPage(String, Object…)
aDetailPage(String, Object…)
aContactPage(String, Object…)
aTagPage(String, Object…)
aPageContext(Resource, Object…)
...
API for creating Components
class Components {
aParSys(String, Object…)
aLink(String, Object…)
aLinkList(String, Object…)
aServiceNavigation(String, Object…)
anImage(String, Object…)
aSlideshow(String, Object…)
aFooter(String, Object…)
aTeaser(String, Object…)
aContactBox(String, Object…)
aRssFeed(String, Object…)
aSearchFilter(String, Object…)
aPager(String, Object…)
aForm(String, Object…)
...
Builder API for Everything
Pages
Components
Resources
Assets
AemClient
TestSetup
TextImage
ControllerTest
<uses>
<creates>
<creates>
<creates><creates>
<creates>
TextImage Test with Builders
public class TextImageControllerTest extends IntegrationTestBase {
@Test

public void testGetImageLegendWithOnlySourceSetReturnsSource() throws Exception {


Resource contentPage = $.aDetailPageWithParents(“/content/foobar/ko/home/content”);

Resource target = $.aResource(contentPage.getPath() + "/jcr:content/textimage");

$.anImage(contentPage.getPath() + "/jcr:content/textimage/image","source","expected");



TextImageController testObj = new TextImageController().setup(
$.aPageContextWithComponent(target, contentPage));
assertEquals("expected", testObj.getImageLegend());
}
TextImage Test with Builders
public class TextImageControllerTest extends IntegrationTestBase {
@Test

public void testGetImageLegendWithOnlySourceSetReturnsSource() throws Exception {


Resource contentPage = $.aDetailPageWithParents(“/content/foobar/ko/home/content”);

Resource target = $.aResource(contentPage.getPath() + "/jcr:content/textimage");

$.anImage(contentPage.getPath() + "/jcr:content/textimage/image","source","expected");



TextImageController testObj = new TextImageController().setup(
$.aPageContextWithComponent(target, contentPage));
assertEquals("expected", testObj.getImageLegend());
}
Assemble
TextImage Test with Builders
public class TextImageControllerTest extends IntegrationTestBase {
@Test

public void testGetImageLegendWithOnlySourceSetReturnsSource() throws Exception {


Resource contentPage = $.aDetailPageWithParents(“/content/foobar/ko/home/content”);

Resource target = $.aResource(contentPage.getPath() + "/jcr:content/textimage");

$.anImage(contentPage.getPath() + "/jcr:content/textimage/image","source","expected");



TextImageController testObj = new TextImageController().setup(
$.aPageContextWithComponent(target, contentPage));
assertEquals("expected", testObj.getImageLegend());
}
Assemble
Act
TextImage Test with Builders
public class TextImageControllerTest extends IntegrationTestBase {
@Test

public void testGetImageLegendWithOnlySourceSetReturnsSource() throws Exception {


Resource contentPage = $.aDetailPageWithParents(“/content/foobar/ko/home/content”);

Resource target = $.aResource(contentPage.getPath() + "/jcr:content/textimage");

$.anImage(contentPage.getPath() + "/jcr:content/textimage/image","source","expected");



TextImageController testObj = new TextImageController().setup(
$.aPageContextWithComponent(target, contentPage));
assertEquals("expected", testObj.getImageLegend());
}
Assemble
Assert
Act
“Fluent Builder” makes use simple
• Fluent Builder API
• Conciseness: Fluent syntax, smart defaults, var args
• Uniformity: Consistent semantics
• Pervasiveness: Availability everywhere
Map<String, String> httpMapping = new Properties(
DATA_INPUT_SUB_PATH, new TestFile("file-delivery-test/input").getPath(),
DATA_OUTPUT_SUB_PATH, new TestFile("file-delivery-test/output").getPath()).toMap();
$.service(
new FileDeliveryService(),
$.aPathMappingService().withMapping(new PathMapping().putAll(httpMapping)));
<R> R service(R service, Object... requiredServices) throws Exception;
<R> R service(IServiceBuilder<R> builder, Object... requiredServices) throws Exception;
“Fluent Builder” makes use simple
• Fluent Builder API
• Conciseness: Fluent syntax, smart defaults, var args
• Uniformity: Consistent semantics
• Pervasiveness: Availability everywhere
Map<String, String> httpMapping = new Properties(
DATA_INPUT_SUB_PATH, new TestFile("file-delivery-test/input").getPath(),
DATA_OUTPUT_SUB_PATH, new TestFile("file-delivery-test/output").getPath()).toMap();
$.service(
new FileDeliveryService(),
$.aPathMappingService().withMapping(new PathMapping().putAll(httpMapping)));
<R> R service(R service, Object... requiredServices) throws Exception;
<R> R service(IServiceBuilder<R> builder, Object... requiredServices) throws Exception;
“Fluent Builder” makes use simple
• Fluent Builder API
• Conciseness: Fluent syntax, smart defaults, var args
• Uniformity: Consistent semantics
• Pervasiveness: Availability everywhere
Map<String, String> httpMapping = new Properties(
DATA_INPUT_SUB_PATH, new TestFile("file-delivery-test/input").getPath(),
DATA_OUTPUT_SUB_PATH, new TestFile("file-delivery-test/output").getPath()).toMap();
$.service(
new FileDeliveryService(),
$.aPathMappingService().withMapping(new PathMapping().putAll(httpMapping)));
<R> R service(R service, Object... requiredServices) throws Exception;
<R> R service(IServiceBuilder<R> builder, Object... requiredServices) throws Exception;
“Fluent Builder” makes use simple
• Fluent Builder API
• Conciseness: Fluent syntax, smart defaults, var args
• Uniformity: Consistent semantics
• Pervasiveness: Availability everywhere
Map<String, String> httpMapping = new Properties(
DATA_INPUT_SUB_PATH, new TestFile("file-delivery-test/input").getPath(),
DATA_OUTPUT_SUB_PATH, new TestFile("file-delivery-test/output").getPath()).toMap();
$.service(
new FileDeliveryService(),
$.aPathMappingService().withMapping(new PathMapping().putAll(httpMapping)));
<R> R service(R service, Object... requiredServices) throws Exception;
<R> R service(IServiceBuilder<R> builder, Object... requiredServices) throws Exception;
“Dynamic Proxy” makes it available
Assets
<creates>
<creates>
Pages
Components
Resources
AemClient
Integration
TestBase
TextImage
ControllerTest
<creates><creates>
<creates>
“Dynamic Proxy” makes it available
ITestSetup
IPagesIComponents IResourcesIAssetsIAemClient
Assets
<creates>
<creates>
Pages
Components
Resources
AemClient
Integration
TestBase
TextImage
ControllerTest
<creates><creates>
<creates>
“Dynamic Proxy” makes it available
ITestSetup
IPagesIComponents IResourcesIAssetsIAemClient
Assets
<creates>
<creates>
Pages
Components
Resources
AemClient
Integration
TestBase
TextImage
ControllerTest
<creates><creates>
<creates>
<owns>
“Dynamic Proxy” makes it available
ITestSetup
IPagesIComponents IResourcesIAssetsIAemClient
<creates>
<creates> MockPages
MockComponents
MockResources
MockAssets
MockClient
UnitTest
TestBase
<owns>
TextImage
ControllerTest
<creates>
<creates><creates>
Initialize Integration Test Context
public abstract class IntegrationTestBase {



public static final String MANDANT_TESTS = “/content/foobar”;

public static final Locale TEST_LOCALE = new Locale("ko");

private final Set<String> resourcesToDelete = new HashSet<>();
public ITestSetup $;

protected SlingClient slingClient;

protected ResourceResolver resolver;



@Before

public void setUpTestContext() throws Exception {

slingClient = new SlingClient(SERVER_URL);

resolver = new ResourceResolverImpl(slingClient);

IResources resources = new Resources(slingClient, resolver);
IPages pages = new Pages(slingClient, resolver);

IComponents components = new Components(resources);

$ = SetupFactory.create(ITestSetup.class).getSetup(resources, pages, components,
new Assets(resources), new AemClient(resolver), new Services(components));
slingClient.deleteResource(MANDANT_TESTS);
}
Initialize Integration Test Context
public abstract class IntegrationTestBase {



public static final String MANDANT_TESTS = “/content/foobar”;

public static final Locale TEST_LOCALE = new Locale("ko");

private final Set<String> resourcesToDelete = new HashSet<>();
public ITestSetup $;

protected SlingClient slingClient;

protected ResourceResolver resolver;



@Before

public void setUpTestContext() throws Exception {

slingClient = new SlingClient(SERVER_URL);

resolver = new ResourceResolverImpl(slingClient);

IResources resources = new Resources(slingClient, resolver);
IPages pages = new Pages(slingClient, resolver);

IComponents components = new Components(resources);

$ = SetupFactory.create(ITestSetup.class).getSetup(resources, pages, components,
new Assets(resources), new AemClient(resolver), new Services(components));
slingClient.deleteResource(MANDANT_TESTS);
}
Initialize Unit Test Context
public abstract class UnitTestBase {



public static final String MANDANT_TESTS = “/content/foobar”;

public static final Locale TEST_LOCALE = new Locale("ko");

public ITestSetup $;



@Before

public void setUpTestContext() throws Exception {

Locale.setDefault(TEST_LOCALE);

IAemClient client = new MockAemClient();

IResources resources = new MockResources(client);

IPages pages = new MockPages(resources, client);

IAssets assets = new MockAssets(resources, client);

IRequest request = new MockRequest(resources, client);

IComponents components = new MockComponents(resources);

$ = SetupFactory.create(ITestSetup.class).getSetup(client, resources, assets,
request, pages, components, new MockJspTags(resources, pages),
new MockServices());

}
Initialize Unit Test Context
public abstract class UnitTestBase {



public static final String MANDANT_TESTS = “/content/foobar”;

public static final Locale TEST_LOCALE = new Locale("ko");

public ITestSetup $;



@Before

public void setUpTestContext() throws Exception {

Locale.setDefault(TEST_LOCALE);

IAemClient client = new MockAemClient();

IResources resources = new MockResources(client);

IPages pages = new MockPages(resources, client);

IAssets assets = new MockAssets(resources, client);

IRequest request = new MockRequest(resources, client);

IComponents components = new MockComponents(resources);

$ = SetupFactory.create(ITestSetup.class).getSetup(client, resources, assets,
request, pages, components, new MockJspTags(resources, pages),
new MockServices());

}
Development Cycle with AEM
New
Integrated
Test
Copy Test
to
UnitTests
Adapt
Mocks
Test-drive
New Feature
Write Acceptance Test first
@Test
public void testIsShowTitleBelowWithImageAndSizeNormalAndPositionLeftReturnsTrue() {
Resource contentPage = $.aDetailPageWithParents(“/content/foobar/ko/home/content");
Resource asset = $.anAsset("/content/dam/quatico-test/test-image.jpg");
Resource target = $.aResource(contentPage.getPath() + "/jcr:content/textimage",
"text", "<b>hello</b>");
$.anImage(contentPage.getPath() + "/jcr:content/textimage/image",
FILE_REFERENCE, asset.getPath(),
"imageSize", NORMAL,
"imagePosition", LEFT_ABOVE);
TextImageController testObj = new TextImageController().setup(
$.aPageContextWithComponent(target, contentPage));
assertTrue(testObj.isShowTitleBelow());
}
TDD away, with Unit Tests
@Test
public void testIsShowTitleBelowWithImagePresentAndSizeNormalAndPositionLeftReturnsTrue() throws Exception {
Resource asset = $.anAsset("/content/dam/foo.jpg");
Resource target = aTextImage("/a/b/c/jcr:content/textimage", "text", "<b>hello</b>");
$.anImage(“/a/b/c/jcr:content/textimage/image",
FILE_REFERENCE, asset.getPath(), "imageSize", NORMAL, "imagePosition", LEFT_ABOVE);
assertTrue(aTextImageController(target).isShowTitleBelow());
}
@Test
public void testIsShowTitleBelowWithImagePresentAndSizeSmallAndPositionLeftReturnFalse() throws Exception {
Resource asset = $.anAsset("/content/dam/foo.jpg");
Resource target = aTextImage("/a/b/c/jcr:content/textimage", "text", "<b>hello</b>");
$.anImage("/a/b/c/jcr:content/textimage/image",
FILE_REFERENCE, asset.getPath(), "imageSize", SMALL, "imagePosition", LEFT_ABOVE);
assertFalse(aTextImageController(target).isShowTitleBelow());
}
@Test
public void testIsShowTitleBelowWithNoImagePresentReturnsFalse() throws Exception {
Resource target = aTextImage("/a/b/c/jcr:content/textimage", "text", "<b>hello</b>");
assertFalse(aTextImageController(target).isShowTitleBelow());
}
@Test
public void testValidateWithValidTextResourceYieldsOk() throws Exception {
Resource target = aTextImage("/a/b/c/jcr:content/textimage", "text", "<b>hello</b>");
assertStatusEquals(Ok(), aTextImageController(target).validate());
}
@Test
public void testValidateWithEmptyTextResourceYieldsWarning() throws Exception {
Resource target = aTextImage("/a/b/c");
Status actual = aTextImageController(target).validate();
assertTrue(actual.isWarning());
}
Unit Testing with AEM
Mock AEM
JCR
foo() : Foo
bar() : Bar
zip: Zip
zap : Zap
TextImage
testFooWithNull()
testFooWithOk()
testBarWithEmpty()
testBarWithResult()
TextImageTest
Client
Unit Testing with AEM
Round trip: ~5000 unit tests ~30s
Mock AEM
JCR
foo() : Foo
bar() : Bar
zip: Zip
zap : Zap
TextImage
testFooWithNull()
testFooWithOk()
testBarWithEmpty()
testBarWithResult()
TextImageTest
Client
Conclusion: No excuses
• Best practices for initializing, stubbing and
mocking objects are made consistently available
• Test setups cost virtually nothing
• You write more simple, reliable, maintainable tests
• Listen to their feedback more frequently
• New developers quickly adapt to best practices
Yes, it’s possible
UT
Performance Testing
Integration
Testing
Human Testing
End-To-End Testing
HT
Integration Testing
Performance
Testing
Unit Testing
End-To-End Testing
Thank You.
jan.wloka@quatico.com
@crashtester

More Related Content

What's hot

Open Source Ajax Solution @OSDC.tw 2009
Open Source Ajax  Solution @OSDC.tw 2009Open Source Ajax  Solution @OSDC.tw 2009
Open Source Ajax Solution @OSDC.tw 2009Robbie Cheng
 
Crossing platforms with JavaScript & React
Crossing platforms with JavaScript & React Crossing platforms with JavaScript & React
Crossing platforms with JavaScript & React Robert DeLuca
 
Node.js in action
Node.js in actionNode.js in action
Node.js in actionSimon Su
 
#36.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_재직자환급교육,실업자교육,국비지원교육, 자바교육,구...
#36.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_재직자환급교육,실업자교육,국비지원교육, 자바교육,구...#36.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_재직자환급교육,실업자교육,국비지원교육, 자바교육,구...
#36.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_재직자환급교육,실업자교육,국비지원교육, 자바교육,구...탑크리에듀(구로디지털단지역3번출구 2분거리)
 
Test and profile your Windows Phone 8 App
Test and profile your Windows Phone 8 AppTest and profile your Windows Phone 8 App
Test and profile your Windows Phone 8 AppMichele Capra
 
How to perform debounce in react
How to perform debounce in reactHow to perform debounce in react
How to perform debounce in reactBOSC Tech Labs
 
Developing application for Windows Phone 7 in TDD
Developing application for Windows Phone 7 in TDDDeveloping application for Windows Phone 7 in TDD
Developing application for Windows Phone 7 in TDDMichele Capra
 
Redux for ReactJS Programmers
Redux for ReactJS ProgrammersRedux for ReactJS Programmers
Redux for ReactJS ProgrammersDavid Rodenas
 
The Ring programming language version 1.7 book - Part 47 of 196
The Ring programming language version 1.7 book - Part 47 of 196The Ring programming language version 1.7 book - Part 47 of 196
The Ring programming language version 1.7 book - Part 47 of 196Mahmoud Samir Fayed
 
Test-Driven Development of AngularJS Applications
Test-Driven Development of AngularJS ApplicationsTest-Driven Development of AngularJS Applications
Test-Driven Development of AngularJS ApplicationsFITC
 
Persisting Data on SQLite using Room
Persisting Data on SQLite using RoomPersisting Data on SQLite using Room
Persisting Data on SQLite using RoomNelson Glauber Leal
 
Basic Tutorial of React for Programmers
Basic Tutorial of React for ProgrammersBasic Tutorial of React for Programmers
Basic Tutorial of React for ProgrammersDavid Rodenas
 
Overview of The Scala Based Lift Web Framework
Overview of The Scala Based Lift Web FrameworkOverview of The Scala Based Lift Web Framework
Overview of The Scala Based Lift Web FrameworkIndicThreads
 
L2 Web App Development Guest Lecture At University of Surrey 20/11/09
L2 Web App Development Guest Lecture At University of Surrey 20/11/09L2 Web App Development Guest Lecture At University of Surrey 20/11/09
L2 Web App Development Guest Lecture At University of Surrey 20/11/09Daniel Bryant
 

What's hot (20)

Nativescript angular
Nativescript angularNativescript angular
Nativescript angular
 
Open Source Ajax Solution @OSDC.tw 2009
Open Source Ajax  Solution @OSDC.tw 2009Open Source Ajax  Solution @OSDC.tw 2009
Open Source Ajax Solution @OSDC.tw 2009
 
Crossing platforms with JavaScript & React
Crossing platforms with JavaScript & React Crossing platforms with JavaScript & React
Crossing platforms with JavaScript & React
 
Node.js in action
Node.js in actionNode.js in action
Node.js in action
 
#36.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_재직자환급교육,실업자교육,국비지원교육, 자바교육,구...
#36.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_재직자환급교육,실업자교육,국비지원교육, 자바교육,구...#36.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_재직자환급교육,실업자교육,국비지원교육, 자바교육,구...
#36.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_재직자환급교육,실업자교육,국비지원교육, 자바교육,구...
 
Test and profile your Windows Phone 8 App
Test and profile your Windows Phone 8 AppTest and profile your Windows Phone 8 App
Test and profile your Windows Phone 8 App
 
How to perform debounce in react
How to perform debounce in reactHow to perform debounce in react
How to perform debounce in react
 
Developing application for Windows Phone 7 in TDD
Developing application for Windows Phone 7 in TDDDeveloping application for Windows Phone 7 in TDD
Developing application for Windows Phone 7 in TDD
 
Easy Button
Easy ButtonEasy Button
Easy Button
 
Redux for ReactJS Programmers
Redux for ReactJS ProgrammersRedux for ReactJS Programmers
Redux for ReactJS Programmers
 
Graphql, REST and Apollo
Graphql, REST and ApolloGraphql, REST and Apollo
Graphql, REST and Apollo
 
The Ring programming language version 1.7 book - Part 47 of 196
The Ring programming language version 1.7 book - Part 47 of 196The Ring programming language version 1.7 book - Part 47 of 196
The Ring programming language version 1.7 book - Part 47 of 196
 
Test-Driven Development of AngularJS Applications
Test-Driven Development of AngularJS ApplicationsTest-Driven Development of AngularJS Applications
Test-Driven Development of AngularJS Applications
 
Persisting Data on SQLite using Room
Persisting Data on SQLite using RoomPersisting Data on SQLite using Room
Persisting Data on SQLite using Room
 
Basic Tutorial of React for Programmers
Basic Tutorial of React for ProgrammersBasic Tutorial of React for Programmers
Basic Tutorial of React for Programmers
 
Basics of AngularJS
Basics of AngularJSBasics of AngularJS
Basics of AngularJS
 
Overview Of Lift Framework
Overview Of Lift FrameworkOverview Of Lift Framework
Overview Of Lift Framework
 
Overview of The Scala Based Lift Web Framework
Overview of The Scala Based Lift Web FrameworkOverview of The Scala Based Lift Web Framework
Overview of The Scala Based Lift Web Framework
 
L2 Web App Development Guest Lecture At University of Surrey 20/11/09
L2 Web App Development Guest Lecture At University of Surrey 20/11/09L2 Web App Development Guest Lecture At University of Surrey 20/11/09
L2 Web App Development Guest Lecture At University of Surrey 20/11/09
 
Sane Async Patterns
Sane Async PatternsSane Async Patterns
Sane Async Patterns
 

Similar to Test-driven Development with AEM

Web UI test automation instruments
Web UI test automation instrumentsWeb UI test automation instruments
Web UI test automation instrumentsArtem Nagornyi
 
Automated%20testing%20with%20Espresso2.x
Automated%20testing%20with%20Espresso2.xAutomated%20testing%20with%20Espresso2.x
Automated%20testing%20with%20Espresso2.xTatsuya Maki
 
Understanding backbonejs
Understanding backbonejsUnderstanding backbonejs
Understanding backbonejsNick Lee
 
比XML更好用的Java Annotation
比XML更好用的Java Annotation比XML更好用的Java Annotation
比XML更好用的Java Annotationjavatwo2011
 
Bare-knuckle web development
Bare-knuckle web developmentBare-knuckle web development
Bare-knuckle web developmentJohannes Brodwall
 
API 통신, Retrofit 대신 Ktor 어떠신가요.pdf
API 통신, Retrofit 대신 Ktor 어떠신가요.pdfAPI 통신, Retrofit 대신 Ktor 어떠신가요.pdf
API 통신, Retrofit 대신 Ktor 어떠신가요.pdfssuserb6c2641
 
Windows 8 metro applications
Windows 8 metro applicationsWindows 8 metro applications
Windows 8 metro applicationsAlex Golesh
 
COScheduler In Depth
COScheduler In DepthCOScheduler In Depth
COScheduler In DepthWO Community
 
Apex Testing and Best Practices
Apex Testing and Best PracticesApex Testing and Best Practices
Apex Testing and Best PracticesJitendra Zaa
 
Integrating Wicket with Java EE 6
Integrating Wicket with Java EE 6Integrating Wicket with Java EE 6
Integrating Wicket with Java EE 6Michael Plöd
 
Javascript Frameworks for Joomla
Javascript Frameworks for JoomlaJavascript Frameworks for Joomla
Javascript Frameworks for JoomlaLuke Summerfield
 
Slaven tomac unit testing in angular js
Slaven tomac   unit testing in angular jsSlaven tomac   unit testing in angular js
Slaven tomac unit testing in angular jsSlaven Tomac
 
Sustaining Test-Driven Development
Sustaining Test-Driven DevelopmentSustaining Test-Driven Development
Sustaining Test-Driven DevelopmentAgileOnTheBeach
 
#18.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_국비지원IT학원/실업자/재직자환급교육/자바/스프링/...
#18.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_국비지원IT학원/실업자/재직자환급교육/자바/스프링/...#18.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_국비지원IT학원/실업자/재직자환급교육/자바/스프링/...
#18.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_국비지원IT학원/실업자/재직자환급교육/자바/스프링/...탑크리에듀(구로디지털단지역3번출구 2분거리)
 

Similar to Test-driven Development with AEM (20)

Jason parsing
Jason parsingJason parsing
Jason parsing
 
Web UI test automation instruments
Web UI test automation instrumentsWeb UI test automation instruments
Web UI test automation instruments
 
Automated%20testing%20with%20Espresso2.x
Automated%20testing%20with%20Espresso2.xAutomated%20testing%20with%20Espresso2.x
Automated%20testing%20with%20Espresso2.x
 
Understanding backbonejs
Understanding backbonejsUnderstanding backbonejs
Understanding backbonejs
 
比XML更好用的Java Annotation
比XML更好用的Java Annotation比XML更好用的Java Annotation
比XML更好用的Java Annotation
 
Bare-knuckle web development
Bare-knuckle web developmentBare-knuckle web development
Bare-knuckle web development
 
API 통신, Retrofit 대신 Ktor 어떠신가요.pdf
API 통신, Retrofit 대신 Ktor 어떠신가요.pdfAPI 통신, Retrofit 대신 Ktor 어떠신가요.pdf
API 통신, Retrofit 대신 Ktor 어떠신가요.pdf
 
Windows 8 metro applications
Windows 8 metro applicationsWindows 8 metro applications
Windows 8 metro applications
 
Requery overview
Requery overviewRequery overview
Requery overview
 
COScheduler In Depth
COScheduler In DepthCOScheduler In Depth
COScheduler In Depth
 
Apex Testing and Best Practices
Apex Testing and Best PracticesApex Testing and Best Practices
Apex Testing and Best Practices
 
Integrating Wicket with Java EE 6
Integrating Wicket with Java EE 6Integrating Wicket with Java EE 6
Integrating Wicket with Java EE 6
 
servlets
servletsservlets
servlets
 
Jsr 303
Jsr 303Jsr 303
Jsr 303
 
Javascript Frameworks for Joomla
Javascript Frameworks for JoomlaJavascript Frameworks for Joomla
Javascript Frameworks for Joomla
 
Slaven tomac unit testing in angular js
Slaven tomac   unit testing in angular jsSlaven tomac   unit testing in angular js
Slaven tomac unit testing in angular js
 
Sustaining Test-Driven Development
Sustaining Test-Driven DevelopmentSustaining Test-Driven Development
Sustaining Test-Driven Development
 
#18.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_국비지원IT학원/실업자/재직자환급교육/자바/스프링/...
#18.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_국비지원IT학원/실업자/재직자환급교육/자바/스프링/...#18.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_국비지원IT학원/실업자/재직자환급교육/자바/스프링/...
#18.스프링프레임워크 & 마이바티스 (Spring Framework, MyBatis)_국비지원IT학원/실업자/재직자환급교육/자바/스프링/...
 
Ajax - a quick introduction
Ajax - a quick introductionAjax - a quick introduction
Ajax - a quick introduction
 
React 101
React 101React 101
React 101
 

Recently uploaded

AI/ML Infra Meetup | Perspective on Deep Learning Framework
AI/ML Infra Meetup | Perspective on Deep Learning FrameworkAI/ML Infra Meetup | Perspective on Deep Learning Framework
AI/ML Infra Meetup | Perspective on Deep Learning FrameworkAlluxio, Inc.
 
GraphAware - Transforming policing with graph-based intelligence analysis
GraphAware - Transforming policing with graph-based intelligence analysisGraphAware - Transforming policing with graph-based intelligence analysis
GraphAware - Transforming policing with graph-based intelligence analysisNeo4j
 
A Python-based approach to data loading in TM1 - Using Airflow as an ETL for TM1
A Python-based approach to data loading in TM1 - Using Airflow as an ETL for TM1A Python-based approach to data loading in TM1 - Using Airflow as an ETL for TM1
A Python-based approach to data loading in TM1 - Using Airflow as an ETL for TM1KnowledgeSeed
 
AI/ML Infra Meetup | Reducing Prefill for LLM Serving in RAG
AI/ML Infra Meetup | Reducing Prefill for LLM Serving in RAGAI/ML Infra Meetup | Reducing Prefill for LLM Serving in RAG
AI/ML Infra Meetup | Reducing Prefill for LLM Serving in RAGAlluxio, Inc.
 
Paketo Buildpacks : la meilleure façon de construire des images OCI? DevopsDa...
Paketo Buildpacks : la meilleure façon de construire des images OCI? DevopsDa...Paketo Buildpacks : la meilleure façon de construire des images OCI? DevopsDa...
Paketo Buildpacks : la meilleure façon de construire des images OCI? DevopsDa...Anthony Dahanne
 
Advanced Flow Concepts Every Developer Should Know
Advanced Flow Concepts Every Developer Should KnowAdvanced Flow Concepts Every Developer Should Know
Advanced Flow Concepts Every Developer Should KnowPeter Caitens
 
A Comprehensive Appium Guide for Hybrid App Automation Testing.pdf
A Comprehensive Appium Guide for Hybrid App Automation Testing.pdfA Comprehensive Appium Guide for Hybrid App Automation Testing.pdf
A Comprehensive Appium Guide for Hybrid App Automation Testing.pdfkalichargn70th171
 
AI/ML Infra Meetup | Improve Speed and GPU Utilization for Model Training & S...
AI/ML Infra Meetup | Improve Speed and GPU Utilization for Model Training & S...AI/ML Infra Meetup | Improve Speed and GPU Utilization for Model Training & S...
AI/ML Infra Meetup | Improve Speed and GPU Utilization for Model Training & S...Alluxio, Inc.
 
TROUBLESHOOTING 9 TYPES OF OUTOFMEMORYERROR
TROUBLESHOOTING 9 TYPES OF OUTOFMEMORYERRORTROUBLESHOOTING 9 TYPES OF OUTOFMEMORYERROR
TROUBLESHOOTING 9 TYPES OF OUTOFMEMORYERRORTier1 app
 
Breaking the Code : A Guide to WhatsApp Business API.pdf
Breaking the Code : A Guide to WhatsApp Business API.pdfBreaking the Code : A Guide to WhatsApp Business API.pdf
Breaking the Code : A Guide to WhatsApp Business API.pdfMeon Technology
 
Accelerate Enterprise Software Engineering with Platformless
Accelerate Enterprise Software Engineering with PlatformlessAccelerate Enterprise Software Engineering with Platformless
Accelerate Enterprise Software Engineering with PlatformlessWSO2
 
SOCRadar Research Team: Latest Activities of IntelBroker
SOCRadar Research Team: Latest Activities of IntelBrokerSOCRadar Research Team: Latest Activities of IntelBroker
SOCRadar Research Team: Latest Activities of IntelBrokerSOCRadar
 
Cyaniclab : Software Development Agency Portfolio.pdf
Cyaniclab : Software Development Agency Portfolio.pdfCyaniclab : Software Development Agency Portfolio.pdf
Cyaniclab : Software Development Agency Portfolio.pdfCyanic lab
 
Using IESVE for Room Loads Analysis - Australia & New Zealand
Using IESVE for Room Loads Analysis - Australia & New ZealandUsing IESVE for Room Loads Analysis - Australia & New Zealand
Using IESVE for Room Loads Analysis - Australia & New ZealandIES VE
 
Agnieszka Andrzejewska - BIM School Course in Kraków
Agnieszka Andrzejewska - BIM School Course in KrakówAgnieszka Andrzejewska - BIM School Course in Kraków
Agnieszka Andrzejewska - BIM School Course in Krakówbim.edu.pl
 
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital Transformation
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital TransformationWSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital Transformation
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital TransformationWSO2
 
Beyond Event Sourcing - Embracing CRUD for Wix Platform - Java.IL
Beyond Event Sourcing - Embracing CRUD for Wix Platform - Java.ILBeyond Event Sourcing - Embracing CRUD for Wix Platform - Java.IL
Beyond Event Sourcing - Embracing CRUD for Wix Platform - Java.ILNatan Silnitsky
 
Studiovity film pre-production and screenwriting software
Studiovity film pre-production and screenwriting softwareStudiovity film pre-production and screenwriting software
Studiovity film pre-production and screenwriting softwareinfo611746
 
Prosigns: Transforming Business with Tailored Technology Solutions
Prosigns: Transforming Business with Tailored Technology SolutionsProsigns: Transforming Business with Tailored Technology Solutions
Prosigns: Transforming Business with Tailored Technology SolutionsProsigns
 

Recently uploaded (20)

AI/ML Infra Meetup | Perspective on Deep Learning Framework
AI/ML Infra Meetup | Perspective on Deep Learning FrameworkAI/ML Infra Meetup | Perspective on Deep Learning Framework
AI/ML Infra Meetup | Perspective on Deep Learning Framework
 
GraphAware - Transforming policing with graph-based intelligence analysis
GraphAware - Transforming policing with graph-based intelligence analysisGraphAware - Transforming policing with graph-based intelligence analysis
GraphAware - Transforming policing with graph-based intelligence analysis
 
A Python-based approach to data loading in TM1 - Using Airflow as an ETL for TM1
A Python-based approach to data loading in TM1 - Using Airflow as an ETL for TM1A Python-based approach to data loading in TM1 - Using Airflow as an ETL for TM1
A Python-based approach to data loading in TM1 - Using Airflow as an ETL for TM1
 
AI/ML Infra Meetup | Reducing Prefill for LLM Serving in RAG
AI/ML Infra Meetup | Reducing Prefill for LLM Serving in RAGAI/ML Infra Meetup | Reducing Prefill for LLM Serving in RAG
AI/ML Infra Meetup | Reducing Prefill for LLM Serving in RAG
 
Paketo Buildpacks : la meilleure façon de construire des images OCI? DevopsDa...
Paketo Buildpacks : la meilleure façon de construire des images OCI? DevopsDa...Paketo Buildpacks : la meilleure façon de construire des images OCI? DevopsDa...
Paketo Buildpacks : la meilleure façon de construire des images OCI? DevopsDa...
 
Advanced Flow Concepts Every Developer Should Know
Advanced Flow Concepts Every Developer Should KnowAdvanced Flow Concepts Every Developer Should Know
Advanced Flow Concepts Every Developer Should Know
 
A Comprehensive Appium Guide for Hybrid App Automation Testing.pdf
A Comprehensive Appium Guide for Hybrid App Automation Testing.pdfA Comprehensive Appium Guide for Hybrid App Automation Testing.pdf
A Comprehensive Appium Guide for Hybrid App Automation Testing.pdf
 
AI/ML Infra Meetup | Improve Speed and GPU Utilization for Model Training & S...
AI/ML Infra Meetup | Improve Speed and GPU Utilization for Model Training & S...AI/ML Infra Meetup | Improve Speed and GPU Utilization for Model Training & S...
AI/ML Infra Meetup | Improve Speed and GPU Utilization for Model Training & S...
 
TROUBLESHOOTING 9 TYPES OF OUTOFMEMORYERROR
TROUBLESHOOTING 9 TYPES OF OUTOFMEMORYERRORTROUBLESHOOTING 9 TYPES OF OUTOFMEMORYERROR
TROUBLESHOOTING 9 TYPES OF OUTOFMEMORYERROR
 
Breaking the Code : A Guide to WhatsApp Business API.pdf
Breaking the Code : A Guide to WhatsApp Business API.pdfBreaking the Code : A Guide to WhatsApp Business API.pdf
Breaking the Code : A Guide to WhatsApp Business API.pdf
 
Accelerate Enterprise Software Engineering with Platformless
Accelerate Enterprise Software Engineering with PlatformlessAccelerate Enterprise Software Engineering with Platformless
Accelerate Enterprise Software Engineering with Platformless
 
SOCRadar Research Team: Latest Activities of IntelBroker
SOCRadar Research Team: Latest Activities of IntelBrokerSOCRadar Research Team: Latest Activities of IntelBroker
SOCRadar Research Team: Latest Activities of IntelBroker
 
Cyaniclab : Software Development Agency Portfolio.pdf
Cyaniclab : Software Development Agency Portfolio.pdfCyaniclab : Software Development Agency Portfolio.pdf
Cyaniclab : Software Development Agency Portfolio.pdf
 
Using IESVE for Room Loads Analysis - Australia & New Zealand
Using IESVE for Room Loads Analysis - Australia & New ZealandUsing IESVE for Room Loads Analysis - Australia & New Zealand
Using IESVE for Room Loads Analysis - Australia & New Zealand
 
Agnieszka Andrzejewska - BIM School Course in Kraków
Agnieszka Andrzejewska - BIM School Course in KrakówAgnieszka Andrzejewska - BIM School Course in Kraków
Agnieszka Andrzejewska - BIM School Course in Kraków
 
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital Transformation
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital TransformationWSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital Transformation
WSO2Con2024 - WSO2's IAM Vision: Identity-Led Digital Transformation
 
Beyond Event Sourcing - Embracing CRUD for Wix Platform - Java.IL
Beyond Event Sourcing - Embracing CRUD for Wix Platform - Java.ILBeyond Event Sourcing - Embracing CRUD for Wix Platform - Java.IL
Beyond Event Sourcing - Embracing CRUD for Wix Platform - Java.IL
 
Studiovity film pre-production and screenwriting software
Studiovity film pre-production and screenwriting softwareStudiovity film pre-production and screenwriting software
Studiovity film pre-production and screenwriting software
 
Corporate Management | Session 3 of 3 | Tendenci AMS
Corporate Management | Session 3 of 3 | Tendenci AMSCorporate Management | Session 3 of 3 | Tendenci AMS
Corporate Management | Session 3 of 3 | Tendenci AMS
 
Prosigns: Transforming Business with Tailored Technology Solutions
Prosigns: Transforming Business with Tailored Technology SolutionsProsigns: Transforming Business with Tailored Technology Solutions
Prosigns: Transforming Business with Tailored Technology Solutions
 

Test-driven Development with AEM

  • 1. TDD with AEM Jan Wloka jan.wloka@quatico.com Quatico Solutions AG
  • 2.
  • 3.
  • 5. Text Image Component JSP Template class TextImageItem class TextImageController <creates> <creates> <uses> <uses>
  • 6. Text Image Component MVC JSP Template class TextImageItem class TextImageController <creates> <creates> <uses> <uses>
  • 7. Client Server Author Deploy Create Running the Component JSP Template class TextImageItem class TextImageController
  • 8. Client Server Author Deploy Create Running the Component Round trip: Single feature ~1min JSP Template class TextImageItem class TextImageController
  • 9. Testing Text Image Component AEM Resources App TextImage TextImage Test <creates> <tests>
  • 10. AEM Sling Testing API @Before public void setUp() throws Exception { URL url = new URL(serverUrl); httpClient = new HttpClient(); httpClient.getParams().setAuthenticationPreemptive(true); httpClient.getState().setCredentials(new AuthScope(url.getHost(), url.getPort(), ANY_REALM), getCredentials()); client = new SlingIntegrationTestClient(httpClient); } private void createPage(String path, Properties properties, Template template) throws Exception { String[] pair = splitPathName(path); HttpMethod createReq = new PostMethod(serverUrl + "/bin/wcmcommand"); createReq.setQueryString(new Properties().append("cmd", “createPage").append("title", pair[1]) .append("parentPath", pair[0]).append(TEMPLATE, template.getName()).toPairs()); if (HttpStatus.SC_OK != httpClient.executeMethod(createReq)) { throw new HttpResponseException(createReq.getStatusCode(), createReq.getStatusText()); } if (!properties.isEmpty()) { HttpMethod changeReq = new PostMethod(serverUrl + path + "/" + JCR_CONTENT); changeReq.setQueryString(properties.toPairs(true)); if (HttpStatus.SC_OK != httpClient.executeMethod(changeReq)) { throw new HttpResponseException(changeReq.getStatusCode(), changeReq.getStatusText()); } } } private String createResource(String path, Properties properties) throws IOException { String nodeUrl = serverUrl + path; client.createNode(nodeUrl, properties.<String>toMap()); return nodeUrl; }
  • 11. AEM Sling Testing API @Before public void setUp() throws Exception { URL url = new URL(serverUrl); httpClient = new HttpClient(); httpClient.getParams().setAuthenticationPreemptive(true); httpClient.getState().setCredentials(new AuthScope(url.getHost(), url.getPort(), ANY_REALM), getCredentials()); client = new SlingIntegrationTestClient(httpClient); } private void createPage(String path, Properties properties, Template template) throws Exception { String[] pair = splitPathName(path); HttpMethod createReq = new PostMethod(serverUrl + "/bin/wcmcommand"); createReq.setQueryString(new Properties().append("cmd", “createPage").append("title", pair[1]) .append("parentPath", pair[0]).append(TEMPLATE, template.getName()).toPairs()); if (HttpStatus.SC_OK != httpClient.executeMethod(createReq)) { throw new HttpResponseException(createReq.getStatusCode(), createReq.getStatusText()); } if (!properties.isEmpty()) { HttpMethod changeReq = new PostMethod(serverUrl + path + "/" + JCR_CONTENT); changeReq.setQueryString(properties.toPairs(true)); if (HttpStatus.SC_OK != httpClient.executeMethod(changeReq)) { throw new HttpResponseException(changeReq.getStatusCode(), changeReq.getStatusText()); } } } private String createResource(String path, Properties properties) throws IOException { String nodeUrl = serverUrl + path; client.createNode(nodeUrl, properties.<String>toMap()); return nodeUrl; } Setup Client
  • 12. AEM Sling Testing API @Before public void setUp() throws Exception { URL url = new URL(serverUrl); httpClient = new HttpClient(); httpClient.getParams().setAuthenticationPreemptive(true); httpClient.getState().setCredentials(new AuthScope(url.getHost(), url.getPort(), ANY_REALM), getCredentials()); client = new SlingIntegrationTestClient(httpClient); } private void createPage(String path, Properties properties, Template template) throws Exception { String[] pair = splitPathName(path); HttpMethod createReq = new PostMethod(serverUrl + "/bin/wcmcommand"); createReq.setQueryString(new Properties().append("cmd", “createPage").append("title", pair[1]) .append("parentPath", pair[0]).append(TEMPLATE, template.getName()).toPairs()); if (HttpStatus.SC_OK != httpClient.executeMethod(createReq)) { throw new HttpResponseException(createReq.getStatusCode(), createReq.getStatusText()); } if (!properties.isEmpty()) { HttpMethod changeReq = new PostMethod(serverUrl + path + "/" + JCR_CONTENT); changeReq.setQueryString(properties.toPairs(true)); if (HttpStatus.SC_OK != httpClient.executeMethod(changeReq)) { throw new HttpResponseException(changeReq.getStatusCode(), changeReq.getStatusText()); } } } private String createResource(String path, Properties properties) throws IOException { String nodeUrl = serverUrl + path; client.createNode(nodeUrl, properties.<String>toMap()); return nodeUrl; } Create Page
  • 13. AEM Sling Testing API @Before public void setUp() throws Exception { URL url = new URL(serverUrl); httpClient = new HttpClient(); httpClient.getParams().setAuthenticationPreemptive(true); httpClient.getState().setCredentials(new AuthScope(url.getHost(), url.getPort(), ANY_REALM), getCredentials()); client = new SlingIntegrationTestClient(httpClient); } private void createPage(String path, Properties properties, Template template) throws Exception { String[] pair = splitPathName(path); HttpMethod createReq = new PostMethod(serverUrl + "/bin/wcmcommand"); createReq.setQueryString(new Properties().append("cmd", “createPage").append("title", pair[1]) .append("parentPath", pair[0]).append(TEMPLATE, template.getName()).toPairs()); if (HttpStatus.SC_OK != httpClient.executeMethod(createReq)) { throw new HttpResponseException(createReq.getStatusCode(), createReq.getStatusText()); } if (!properties.isEmpty()) { HttpMethod changeReq = new PostMethod(serverUrl + path + "/" + JCR_CONTENT); changeReq.setQueryString(properties.toPairs(true)); if (HttpStatus.SC_OK != httpClient.executeMethod(changeReq)) { throw new HttpResponseException(changeReq.getStatusCode(), changeReq.getStatusText()); } } } private String createResource(String path, Properties properties) throws IOException { String nodeUrl = serverUrl + path; client.createNode(nodeUrl, properties.<String>toMap()); return nodeUrl; } Setup Resource
  • 14. Test: Image caption is shown @Test public void testGetImageLegendWithSourceAndCaptionReturnsBoth() throws Exception { ResourceResolver resolver = new ResourceResolverImpl(client); createPage(“/content/foobar”, new Properties(), PageType.PRESENCE.getTemplate()); createPage(“/content/foobar/ko”, new Properties(), PageType.LANGUAGE.getTemplate()); createPage(“/content/foobar/ko/home", new Properties(), PageType.HOME.getTemplate()); createPage(“/content/foobar/ko/home/content", new Properties(), PageType.DETAIL.getTemplate()); Resource contentPage = resolver.getResource(“/content/foobar/ko/home/content"); createResource(contentPage.getPath() + "/jcr:content/textimage", new Properties()); Resource target = resolver.getResource(contentPage.getPath() + "/jcr:content/textimage"); Properties imageProps = new Properties() .append("source", "<sourceValue>") .append("caption", "titleValue") .append("sling:resourceType", ComponentType.IMAGE) .append(JCR_LASTMODIFIED, new Time().format(DateFormat.GMT)) .append(JCR_LAST_MODIFIED_BY, "admin"); createResource(contentPage.getPath() + "/jcr:content/textimage/image", imageProps); TextImageController testObj = new TextImageController().setup(createContext(target, contentPage)); assertEquals("titleValue<br />&lt;sourceValue&gt;", testObj.getImageLegend()); }
  • 15. Test: Image caption is shown @Test public void testGetImageLegendWithSourceAndCaptionReturnsBoth() throws Exception { ResourceResolver resolver = new ResourceResolverImpl(client); createPage(“/content/foobar”, new Properties(), PageType.PRESENCE.getTemplate()); createPage(“/content/foobar/ko”, new Properties(), PageType.LANGUAGE.getTemplate()); createPage(“/content/foobar/ko/home", new Properties(), PageType.HOME.getTemplate()); createPage(“/content/foobar/ko/home/content", new Properties(), PageType.DETAIL.getTemplate()); Resource contentPage = resolver.getResource(“/content/foobar/ko/home/content"); createResource(contentPage.getPath() + "/jcr:content/textimage", new Properties()); Resource target = resolver.getResource(contentPage.getPath() + "/jcr:content/textimage"); Properties imageProps = new Properties() .append("source", "<sourceValue>") .append("caption", "titleValue") .append("sling:resourceType", ComponentType.IMAGE) .append(JCR_LASTMODIFIED, new Time().format(DateFormat.GMT)) .append(JCR_LAST_MODIFIED_BY, "admin"); createResource(contentPage.getPath() + "/jcr:content/textimage/image", imageProps); TextImageController testObj = new TextImageController().setup(createContext(target, contentPage)); assertEquals("titleValue<br />&lt;sourceValue&gt;", testObj.getImageLegend()); } Assemble
  • 16. Test: Image caption is shown @Test public void testGetImageLegendWithSourceAndCaptionReturnsBoth() throws Exception { ResourceResolver resolver = new ResourceResolverImpl(client); createPage(“/content/foobar”, new Properties(), PageType.PRESENCE.getTemplate()); createPage(“/content/foobar/ko”, new Properties(), PageType.LANGUAGE.getTemplate()); createPage(“/content/foobar/ko/home", new Properties(), PageType.HOME.getTemplate()); createPage(“/content/foobar/ko/home/content", new Properties(), PageType.DETAIL.getTemplate()); Resource contentPage = resolver.getResource(“/content/foobar/ko/home/content"); createResource(contentPage.getPath() + "/jcr:content/textimage", new Properties()); Resource target = resolver.getResource(contentPage.getPath() + "/jcr:content/textimage"); Properties imageProps = new Properties() .append("source", "<sourceValue>") .append("caption", "titleValue") .append("sling:resourceType", ComponentType.IMAGE) .append(JCR_LASTMODIFIED, new Time().format(DateFormat.GMT)) .append(JCR_LAST_MODIFIED_BY, "admin"); createResource(contentPage.getPath() + "/jcr:content/textimage/image", imageProps); TextImageController testObj = new TextImageController().setup(createContext(target, contentPage)); assertEquals("titleValue<br />&lt;sourceValue&gt;", testObj.getImageLegend()); } Assemble Act
  • 17. Test: Image caption is shown @Test public void testGetImageLegendWithSourceAndCaptionReturnsBoth() throws Exception { ResourceResolver resolver = new ResourceResolverImpl(client); createPage(“/content/foobar”, new Properties(), PageType.PRESENCE.getTemplate()); createPage(“/content/foobar/ko”, new Properties(), PageType.LANGUAGE.getTemplate()); createPage(“/content/foobar/ko/home", new Properties(), PageType.HOME.getTemplate()); createPage(“/content/foobar/ko/home/content", new Properties(), PageType.DETAIL.getTemplate()); Resource contentPage = resolver.getResource(“/content/foobar/ko/home/content"); createResource(contentPage.getPath() + "/jcr:content/textimage", new Properties()); Resource target = resolver.getResource(contentPage.getPath() + "/jcr:content/textimage"); Properties imageProps = new Properties() .append("source", "<sourceValue>") .append("caption", "titleValue") .append("sling:resourceType", ComponentType.IMAGE) .append(JCR_LASTMODIFIED, new Time().format(DateFormat.GMT)) .append(JCR_LAST_MODIFIED_BY, "admin"); createResource(contentPage.getPath() + "/jcr:content/textimage/image", imageProps); TextImageController testObj = new TextImageController().setup(createContext(target, contentPage)); assertEquals("titleValue<br />&lt;sourceValue&gt;", testObj.getImageLegend()); } Assemble Act Assert
  • 18. Integrated Tests with AEM Author Publish boot2 JCR JSP JS/CSS foo() : Foo bar() : Bar zip: Zip zap : Zap TextImage testFooWithNull() testFooWithOk() testBarWithEmpty() testBarWithResult() TextImageTest Request Response Client Server
  • 19. Integrated Tests with AEM Round trip: ~80 tested features ~2m30s Author Publish boot2 JCR JSP JS/CSS foo() : Foo bar() : Bar zip: Zip zap : Zap TextImage testFooWithNull() testFooWithOk() testBarWithEmpty() testBarWithResult() TextImageTest Request Response Client Server
  • 20. It works, but still hurts! Client Server AEM App Test Test Test Test Test
  • 21. It works, but still hurts! Client Server AEM App Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test TestTest Test Test Test Test Test Test Test Test TestTest Test Test Test Test Test Test Test Test TestTest Test Test Test Test Test Test Test TestTest Test Test Test Test Test Test Test Test
  • 22. It works, but still hurts! Client Server AEM App Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test Test TestTest Test Test Test Test Test Test Test Test TestTest Test Test Test Test Test Test Test Test TestTest Test Test Test Test Test Test Test TestTest Test Test Test Test Test Test Test Test Delay (or prevent) feedback Hard to write Difficult to maintain Flaky results Low coverage
  • 23. A unified testing API AEM App App Test API
  • 24. A unified testing API Test API AEM Resources App TextImage TextImage Test <uses> <tests>
  • 25. API for creating Pages class Pages { aPresencePage(String, Object…) aLanguagePage(String, Object…) aHomePage(String, Object…) aNewsPage(String, Object…) aRssFeedPage(String, Object…) aDetailPage(String, Object…) aContactPage(String, Object…) aTagPage(String, Object…) aPageContext(Resource, Object…) ...
  • 26. API for creating Components class Components { aParSys(String, Object…) aLink(String, Object…) aLinkList(String, Object…) aServiceNavigation(String, Object…) anImage(String, Object…) aSlideshow(String, Object…) aFooter(String, Object…) aTeaser(String, Object…) aContactBox(String, Object…) aRssFeed(String, Object…) aSearchFilter(String, Object…) aPager(String, Object…) aForm(String, Object…) ...
  • 27. Builder API for Everything Pages Components Resources Assets AemClient TestSetup TextImage ControllerTest <uses> <creates> <creates> <creates><creates> <creates>
  • 28. TextImage Test with Builders public class TextImageControllerTest extends IntegrationTestBase { @Test
 public void testGetImageLegendWithOnlySourceSetReturnsSource() throws Exception { 
 Resource contentPage = $.aDetailPageWithParents(“/content/foobar/ko/home/content”);
 Resource target = $.aResource(contentPage.getPath() + "/jcr:content/textimage");
 $.anImage(contentPage.getPath() + "/jcr:content/textimage/image","source","expected");
 
 TextImageController testObj = new TextImageController().setup( $.aPageContextWithComponent(target, contentPage)); assertEquals("expected", testObj.getImageLegend()); }
  • 29. TextImage Test with Builders public class TextImageControllerTest extends IntegrationTestBase { @Test
 public void testGetImageLegendWithOnlySourceSetReturnsSource() throws Exception { 
 Resource contentPage = $.aDetailPageWithParents(“/content/foobar/ko/home/content”);
 Resource target = $.aResource(contentPage.getPath() + "/jcr:content/textimage");
 $.anImage(contentPage.getPath() + "/jcr:content/textimage/image","source","expected");
 
 TextImageController testObj = new TextImageController().setup( $.aPageContextWithComponent(target, contentPage)); assertEquals("expected", testObj.getImageLegend()); } Assemble
  • 30. TextImage Test with Builders public class TextImageControllerTest extends IntegrationTestBase { @Test
 public void testGetImageLegendWithOnlySourceSetReturnsSource() throws Exception { 
 Resource contentPage = $.aDetailPageWithParents(“/content/foobar/ko/home/content”);
 Resource target = $.aResource(contentPage.getPath() + "/jcr:content/textimage");
 $.anImage(contentPage.getPath() + "/jcr:content/textimage/image","source","expected");
 
 TextImageController testObj = new TextImageController().setup( $.aPageContextWithComponent(target, contentPage)); assertEquals("expected", testObj.getImageLegend()); } Assemble Act
  • 31. TextImage Test with Builders public class TextImageControllerTest extends IntegrationTestBase { @Test
 public void testGetImageLegendWithOnlySourceSetReturnsSource() throws Exception { 
 Resource contentPage = $.aDetailPageWithParents(“/content/foobar/ko/home/content”);
 Resource target = $.aResource(contentPage.getPath() + "/jcr:content/textimage");
 $.anImage(contentPage.getPath() + "/jcr:content/textimage/image","source","expected");
 
 TextImageController testObj = new TextImageController().setup( $.aPageContextWithComponent(target, contentPage)); assertEquals("expected", testObj.getImageLegend()); } Assemble Assert Act
  • 32. “Fluent Builder” makes use simple • Fluent Builder API • Conciseness: Fluent syntax, smart defaults, var args • Uniformity: Consistent semantics • Pervasiveness: Availability everywhere Map<String, String> httpMapping = new Properties( DATA_INPUT_SUB_PATH, new TestFile("file-delivery-test/input").getPath(), DATA_OUTPUT_SUB_PATH, new TestFile("file-delivery-test/output").getPath()).toMap(); $.service( new FileDeliveryService(), $.aPathMappingService().withMapping(new PathMapping().putAll(httpMapping))); <R> R service(R service, Object... requiredServices) throws Exception; <R> R service(IServiceBuilder<R> builder, Object... requiredServices) throws Exception;
  • 33. “Fluent Builder” makes use simple • Fluent Builder API • Conciseness: Fluent syntax, smart defaults, var args • Uniformity: Consistent semantics • Pervasiveness: Availability everywhere Map<String, String> httpMapping = new Properties( DATA_INPUT_SUB_PATH, new TestFile("file-delivery-test/input").getPath(), DATA_OUTPUT_SUB_PATH, new TestFile("file-delivery-test/output").getPath()).toMap(); $.service( new FileDeliveryService(), $.aPathMappingService().withMapping(new PathMapping().putAll(httpMapping))); <R> R service(R service, Object... requiredServices) throws Exception; <R> R service(IServiceBuilder<R> builder, Object... requiredServices) throws Exception;
  • 34. “Fluent Builder” makes use simple • Fluent Builder API • Conciseness: Fluent syntax, smart defaults, var args • Uniformity: Consistent semantics • Pervasiveness: Availability everywhere Map<String, String> httpMapping = new Properties( DATA_INPUT_SUB_PATH, new TestFile("file-delivery-test/input").getPath(), DATA_OUTPUT_SUB_PATH, new TestFile("file-delivery-test/output").getPath()).toMap(); $.service( new FileDeliveryService(), $.aPathMappingService().withMapping(new PathMapping().putAll(httpMapping))); <R> R service(R service, Object... requiredServices) throws Exception; <R> R service(IServiceBuilder<R> builder, Object... requiredServices) throws Exception;
  • 35. “Fluent Builder” makes use simple • Fluent Builder API • Conciseness: Fluent syntax, smart defaults, var args • Uniformity: Consistent semantics • Pervasiveness: Availability everywhere Map<String, String> httpMapping = new Properties( DATA_INPUT_SUB_PATH, new TestFile("file-delivery-test/input").getPath(), DATA_OUTPUT_SUB_PATH, new TestFile("file-delivery-test/output").getPath()).toMap(); $.service( new FileDeliveryService(), $.aPathMappingService().withMapping(new PathMapping().putAll(httpMapping))); <R> R service(R service, Object... requiredServices) throws Exception; <R> R service(IServiceBuilder<R> builder, Object... requiredServices) throws Exception;
  • 36. “Dynamic Proxy” makes it available Assets <creates> <creates> Pages Components Resources AemClient Integration TestBase TextImage ControllerTest <creates><creates> <creates>
  • 37. “Dynamic Proxy” makes it available ITestSetup IPagesIComponents IResourcesIAssetsIAemClient Assets <creates> <creates> Pages Components Resources AemClient Integration TestBase TextImage ControllerTest <creates><creates> <creates>
  • 38. “Dynamic Proxy” makes it available ITestSetup IPagesIComponents IResourcesIAssetsIAemClient Assets <creates> <creates> Pages Components Resources AemClient Integration TestBase TextImage ControllerTest <creates><creates> <creates> <owns>
  • 39. “Dynamic Proxy” makes it available ITestSetup IPagesIComponents IResourcesIAssetsIAemClient <creates> <creates> MockPages MockComponents MockResources MockAssets MockClient UnitTest TestBase <owns> TextImage ControllerTest <creates> <creates><creates>
  • 40. Initialize Integration Test Context public abstract class IntegrationTestBase {
 
 public static final String MANDANT_TESTS = “/content/foobar”;
 public static final Locale TEST_LOCALE = new Locale("ko");
 private final Set<String> resourcesToDelete = new HashSet<>(); public ITestSetup $;
 protected SlingClient slingClient;
 protected ResourceResolver resolver;
 
 @Before
 public void setUpTestContext() throws Exception {
 slingClient = new SlingClient(SERVER_URL);
 resolver = new ResourceResolverImpl(slingClient);
 IResources resources = new Resources(slingClient, resolver); IPages pages = new Pages(slingClient, resolver);
 IComponents components = new Components(resources);
 $ = SetupFactory.create(ITestSetup.class).getSetup(resources, pages, components, new Assets(resources), new AemClient(resolver), new Services(components)); slingClient.deleteResource(MANDANT_TESTS); }
  • 41. Initialize Integration Test Context public abstract class IntegrationTestBase {
 
 public static final String MANDANT_TESTS = “/content/foobar”;
 public static final Locale TEST_LOCALE = new Locale("ko");
 private final Set<String> resourcesToDelete = new HashSet<>(); public ITestSetup $;
 protected SlingClient slingClient;
 protected ResourceResolver resolver;
 
 @Before
 public void setUpTestContext() throws Exception {
 slingClient = new SlingClient(SERVER_URL);
 resolver = new ResourceResolverImpl(slingClient);
 IResources resources = new Resources(slingClient, resolver); IPages pages = new Pages(slingClient, resolver);
 IComponents components = new Components(resources);
 $ = SetupFactory.create(ITestSetup.class).getSetup(resources, pages, components, new Assets(resources), new AemClient(resolver), new Services(components)); slingClient.deleteResource(MANDANT_TESTS); }
  • 42. Initialize Unit Test Context public abstract class UnitTestBase {
 
 public static final String MANDANT_TESTS = “/content/foobar”;
 public static final Locale TEST_LOCALE = new Locale("ko");
 public ITestSetup $;
 
 @Before
 public void setUpTestContext() throws Exception {
 Locale.setDefault(TEST_LOCALE);
 IAemClient client = new MockAemClient();
 IResources resources = new MockResources(client);
 IPages pages = new MockPages(resources, client);
 IAssets assets = new MockAssets(resources, client);
 IRequest request = new MockRequest(resources, client);
 IComponents components = new MockComponents(resources);
 $ = SetupFactory.create(ITestSetup.class).getSetup(client, resources, assets, request, pages, components, new MockJspTags(resources, pages), new MockServices());
 }
  • 43. Initialize Unit Test Context public abstract class UnitTestBase {
 
 public static final String MANDANT_TESTS = “/content/foobar”;
 public static final Locale TEST_LOCALE = new Locale("ko");
 public ITestSetup $;
 
 @Before
 public void setUpTestContext() throws Exception {
 Locale.setDefault(TEST_LOCALE);
 IAemClient client = new MockAemClient();
 IResources resources = new MockResources(client);
 IPages pages = new MockPages(resources, client);
 IAssets assets = new MockAssets(resources, client);
 IRequest request = new MockRequest(resources, client);
 IComponents components = new MockComponents(resources);
 $ = SetupFactory.create(ITestSetup.class).getSetup(client, resources, assets, request, pages, components, new MockJspTags(resources, pages), new MockServices());
 }
  • 44. Development Cycle with AEM New Integrated Test Copy Test to UnitTests Adapt Mocks Test-drive New Feature
  • 45. Write Acceptance Test first @Test public void testIsShowTitleBelowWithImageAndSizeNormalAndPositionLeftReturnsTrue() { Resource contentPage = $.aDetailPageWithParents(“/content/foobar/ko/home/content"); Resource asset = $.anAsset("/content/dam/quatico-test/test-image.jpg"); Resource target = $.aResource(contentPage.getPath() + "/jcr:content/textimage", "text", "<b>hello</b>"); $.anImage(contentPage.getPath() + "/jcr:content/textimage/image", FILE_REFERENCE, asset.getPath(), "imageSize", NORMAL, "imagePosition", LEFT_ABOVE); TextImageController testObj = new TextImageController().setup( $.aPageContextWithComponent(target, contentPage)); assertTrue(testObj.isShowTitleBelow()); }
  • 46. TDD away, with Unit Tests @Test public void testIsShowTitleBelowWithImagePresentAndSizeNormalAndPositionLeftReturnsTrue() throws Exception { Resource asset = $.anAsset("/content/dam/foo.jpg"); Resource target = aTextImage("/a/b/c/jcr:content/textimage", "text", "<b>hello</b>"); $.anImage(“/a/b/c/jcr:content/textimage/image", FILE_REFERENCE, asset.getPath(), "imageSize", NORMAL, "imagePosition", LEFT_ABOVE); assertTrue(aTextImageController(target).isShowTitleBelow()); } @Test public void testIsShowTitleBelowWithImagePresentAndSizeSmallAndPositionLeftReturnFalse() throws Exception { Resource asset = $.anAsset("/content/dam/foo.jpg"); Resource target = aTextImage("/a/b/c/jcr:content/textimage", "text", "<b>hello</b>"); $.anImage("/a/b/c/jcr:content/textimage/image", FILE_REFERENCE, asset.getPath(), "imageSize", SMALL, "imagePosition", LEFT_ABOVE); assertFalse(aTextImageController(target).isShowTitleBelow()); } @Test public void testIsShowTitleBelowWithNoImagePresentReturnsFalse() throws Exception { Resource target = aTextImage("/a/b/c/jcr:content/textimage", "text", "<b>hello</b>"); assertFalse(aTextImageController(target).isShowTitleBelow()); } @Test public void testValidateWithValidTextResourceYieldsOk() throws Exception { Resource target = aTextImage("/a/b/c/jcr:content/textimage", "text", "<b>hello</b>"); assertStatusEquals(Ok(), aTextImageController(target).validate()); } @Test public void testValidateWithEmptyTextResourceYieldsWarning() throws Exception { Resource target = aTextImage("/a/b/c"); Status actual = aTextImageController(target).validate(); assertTrue(actual.isWarning()); }
  • 47. Unit Testing with AEM Mock AEM JCR foo() : Foo bar() : Bar zip: Zip zap : Zap TextImage testFooWithNull() testFooWithOk() testBarWithEmpty() testBarWithResult() TextImageTest Client
  • 48. Unit Testing with AEM Round trip: ~5000 unit tests ~30s Mock AEM JCR foo() : Foo bar() : Bar zip: Zip zap : Zap TextImage testFooWithNull() testFooWithOk() testBarWithEmpty() testBarWithResult() TextImageTest Client
  • 49. Conclusion: No excuses • Best practices for initializing, stubbing and mocking objects are made consistently available • Test setups cost virtually nothing • You write more simple, reliable, maintainable tests • Listen to their feedback more frequently • New developers quickly adapt to best practices
  • 50. Yes, it’s possible UT Performance Testing Integration Testing Human Testing End-To-End Testing HT Integration Testing Performance Testing Unit Testing End-To-End Testing