I am writing automated tests, and I want each to retry twice. So I wrote a method:
public void retry(int retries, Retryable retryable) {
for (int i = 0; i < retries; i ) {
try {
retryable.run();
break;
} catch (Exception e) {
log.warn(WARN_TEXT, retryable, (i 1), e);
if (i == retries - 1) {
log.error(ERR_TEXT, retryable, retries, e);
retryable.handleException(e);
throw e;
}
}
}
}
public interface Retryable extends Runnable {
void handleException(Exception e);
}
Now I have couple of test methods, let's write 3 here:
@TestRailID(29828) // 1
@Test(description = "Try saving a filter without a name.",
groups = Group.PREPROD)
public void tryCreatingNoNameFilter() {
Retry.retry(2, new Retryable() {
@Override
public void handleException(Exception e) {
log.error(TEST_RUN_FAIL, 2);
}
@Override
public void run() {
userTriesCreatingNoNameFilter();
}
});
}
@TestRailID(31391) // 2
@Test(description = "Try saving a filter with too long name.",
groups = Group.PREPROD)
public void tryCreatingTooLongFilterName() {
Retry.retry(2, new Retryable() {
@Override
public void handleException(Exception e) {
log.error(TEST_RUN_FAIL, 2);
}
@Override
public void run() {
userTriesCreatingTooLongFilerName();
}
});
}
@TestRailID(29829) // 3
@Test(description = "Create and save a new filter.",
groups = Group.PREPROD)
public void createNewFilter() {
Retry.retry(2, new Retryable() {
@Override
public void handleException(Exception e) {
log.error(TEST_RUN_FAIL, 2);
}
@Override
public void run() {
userTriesCreatingNewFilter();
}
});
}
So we all can see that these methods differ only with run() method implemetation (single line). How can I do it without copy pasting that long blocks of code?
Thank you in advacne :)
CodePudding user response:
To reduce the repetitive blocks and number of lines (and make this overall look cleaner), you could:
Instead of extending
Runnable
, split up the exception handling and the run logic into two separate functional interfaces (see@FunctionalInterface
):@FunctionalInterface interface ExceptionHandler { void handleException(Exception e); }
Runnable
is in fact already a functional interface, so you can stick to this.Then you can write these as lambdas:
Retry.retry( 2, () -> userTriesCreatingTooLongFilerName(), exception -> log.error(TEST_RUN_FAIL, 2) );
As your exception handling seems to be the same for all calls, define it once:
var exceptionHandler = (ExceptionHandler) e -> log.error(TEST_RUN_FAIL, 2); Retry.retry(2, () -> userTriesCreatingNoNameFilter(), exceptionHandler); Retry.retry(2, () -> userTriesCreatingTooLongFilerName(), exceptionHandler); Retry.retry(2, () -> userTriesCreatingNewFilter(), exceptionHandler); // …
Further, alternative options:
Subclass your existing Retryable and pull up the common code.
Add a
default
implementation to your existing interface with the common code.