-
Notifications
You must be signed in to change notification settings - Fork 39
Best practices
This document provides an unordered list of best practices for extending the framework and writing tests. Some of these advices might seem like common sense, some might not.
The testing client sprovide a lot of rules and clients for using one or multiple AEM instances in a generic way. It's also layered, from generic actions in sling to more specific actions in cq and granite (to be merge with cq).
If it looks like there might be a client action in the testing clients, use that.
- Use and compose the provided Junit rules. They're great! Like the CQAuthorPublishClassRule
- Try to avoid the
TestBase
parent class pattern. Anything you can do with parent classes and more, you can do with Junit Rules. Chaining rules looks like this:
public class BaseTestClassRule implements TestRule {
public BaseTestClassRule() {
super();
cqClassRule = new CQClassRule();
author1Rule = ClassRuleUtils.newInstanceRule(true).withRunMode("author");
author2Rule = ClassRuleUtils.newInstanceRule(true).withRunMode("author");
publishRule = ClassRuleUtils.newInstanceRule(true).withRunMode("publish");
defaultReplicationAgentsRule = new DefaultReplicationAgents(author1Rule, publishRule);
ruleChain = RuleChain.outerRule(cqClassRule)
.around(author1Rule)
.around(author2Rule)
.around(publishRule)
.around(defaultReplicationAgentsRule);
}
[...]
@Override
public Statement apply(Statement base, Description description) {
return ruleChain.apply(base, description);
}
}
- Then, instead of extending a
BaseTest
class, apply aBaseTestClassRule
If you find yourself performing a certain action more than once, put that in a utility class or a custom client - they provide a lot of goodies.
Try to use the most top-level clients and utilities provided by the framework - they provide a lot of stuff out-of-the-box (e.g. pointing to the topology under test, authentication, sharing the cookie store, timeouts, etc.)
Avoid using things like HttpGet
directly and use AbstractSlingClient#doGet.
As a rule of thumb, you should never use Thread.sleep()
. Always use AbstractPoller
or better yet, the newer Polling class for trying the action until it returns the expected result.
Always use a specific name-spaced paths. E.g. add tst-
prefix to all the resources created. You will later be able to easily identify and clean the generated content
Make sure tests don't leave side-effects behind. For example:
- Use OsgiInstanceConfig to set an osgi property and restore it at the end to the original value before the test
- Use the Page resource rule that deletes the page at the end
- Clean up/ restore in an
@After
or@AfterClass
anything that was created or changed in the test. Better yet, use a rule (it's like a reusable class that provides before and after )
When writing an end-to-end test, one often times needs to do things like prepare some content or create a user, in order to actually test a certain functionality.
In those cases, obviously separate the setup and cleanup (!) in before/ after methods or their class equivalents. Additionally, try and follow this rule:
- Setup and cleanup should be permissive - e.g. retry a page creation or deletion. Afterall, it's not what the test actually needs to test; there's a different test for page creation.
- On the other hand, make asserts strict - e.g. adding a component to that prepared page shoudl work the first time! (unless specified otherwise in documentation or the expectations of the feature)
- Use sensible values for Polling, based on the expectations.
- Max one order of magnitude higher timeouts - e.g. expected to create the user async in 100ms, you can poll every 250ms, for max 2.5 seconds.
- It's not enough if "it works on my machine". The tests are reused in multiple scenarios, on different types of deployments, with varying resources.
The http testing clients are meant for functional/ integrated http tests. They are relatively high on the testing pyramid, under UI tests, but clearly above unit tests. That means the complexity, the execution time and needed resources are quite high. Try covering as much as possible with unit tests and minimal integration tests. Try the OSGi mocks
or pax exam
, they're great!
InterruptedException
is thrown by blocking methods, e.g. Thread.sleep()
and it's a mechanism to gracefully terminate
a program. If this exception is inhibited (catch without rethrow), the program/test will not respect the SIGINT signal
(e.g. will not stop at CTRL+C). In corner cases, you might need to catch the exception to allow the method to finish
the task, but make sure you rethrow it at the end.
Besides this, throws InterruptedException
is a way to notify the user that the method is blocking (performing a wait).