Test Observability & Engineering effectiveness
“Engineering Effectiveness” has been a central topic of discussion and focus this year
Testing across all interfaces, platforms and architectural components
Product test engineering, Shift-Left testing and digital transformation
Automate tests across all interfaces, platforms and horizontal scaling
Generative AI, Flutter, React Native, Micro-services, Micro-frontends & TestOps
Measure and enhance the efficiency & effectiveness of testing teams & solutions
Offshore Testing Setup as a Service, platform engineering and Modernisation
How to Automate Workflow-Based Applications using Workflow Design Pattern?
Now imagine, you are trying to automate two scenarios
To achieve this, we create two test cases and add similar codes up to the payment page. In the end, we perform respective payment actions and assert for success or failure.
This is one scenario, in a large application, there could be many scenarios where we end up creating a lot of duplicate code against the clean code practices.
There are few industry-standard patterns to overcome this, let’s understand them
1. Chain of responsibility: Pass requests along a chain of handlers. Upon receiving a request, each handler decides either to process the request or to pass it to the next handler in the chain.
Pros:
1. Control the order of request handling
2. Can introduce new handlers into the app without breaking the existing client code
Pros:
1. Swap algorithms used inside an object at runtime.
2. Can isolate the implementation details of an algorithm.
The above two patterns though help avoid code duplication, but it doesn’t help in building reusable workflows. This is where a workflow pattern comes into the picture.
Let us look at a sample workflow:
@Getter
@Setter
@Builder(toBuilder=true)
@NoArgsConstructor
@AllArgsConstructor
public class UseCase {
@Builder.Default private Map<Class, Object> useCases = new ConcurrentHashMap();
@Builder.Default private List<Class> completedStates = new CopyOnWriteArrayList();
public UseCase addToUseCase(Object data) {
useCases.put(data.getClass(), data);
return this;
}
public Q getData(Class tClass) {
return (Q) useCases.get(tClass);
}
public WorkFlowDefinition persistState(WorkFlowDefinition currentState) {
this.completedStates.add(currentState.getClass());
return currentState;
}
public boolean isACompletedState(WorkFlowDefinition workflowDefinition) {
return completedStates.contains(workflowDefinition.getClass());
}
public class HomeDefinition extends WorkFlowDefinition {
protected FulfillCondition selectProduct=
()->{
create().selectProduct(useCase.getData(Product.class).getName());
return this;
};
public HomeDefinition(UseCase useCase) {
super(useCase);
}
@Override
public HomePage create() {
return LayoutInitiator.Page(HomePage.class);
}
@Override
public ProductDefinition next() {
return proceedToNext(selectProduct,new ProductDefinition(useCase));
}
}
public class SingleProductDoc extends WorkFlowDoc {
public SingleProductDoc(UseCase useCase) {
super(useCase);
}
public HomeDefinition selectProduct(){
return new HomeDefinition(useCase);
}
public ProductDefinition product(){
return selectProduct().next();
}
public CheckOutDefinition cart(){
return product().next();
}
public LoginDefinition checkOut() {
return cart().next();
}
public InformationDefinition login(){
return checkOut().next();
}
public ShippingDefinition contact(){
return login().next();
}
public PaymentDefinition payment(){
return contact().next();
}
}
public class PurchaseTests extends WebTest {
@Inject
UseCaseGenerator useCaseGenerator;
@Test(groups = "web")
public void shouldDisplayLoginPageAfterCheckOut() {
workFlowPattern.UseCase useCase=useCaseGenerator.happyPathCase();
Assert.assertEquals("Login",new SingleProductDoc(useCase).checkOut().create().getHeading());
}
@Test(groups = "web")
public void shouldRedirectToInformationPageAfterLogin() {
workFlowPattern.UseCase useCase=useCaseGenerator.happyPathCase();
Assert.assertEquals("Contact information",new SingleProductDoc(useCase).login().create().getHeading());
}
@Test(groups = "web")
public void shouldDisplayPaymentPage() {
workFlowPattern.UseCase useCase=useCaseGenerator.happyPathCase();
Assert.assertEquals("Payment",new SingleProductDoc(useCase).payment().create().getHeading());
}
}
Cons of workflow:
Thanks for reading
“Engineering Effectiveness” has been a central topic of discussion and focus this year
With us, you’re not just a part of a company; you’re part of a movement dedicated to pushing boundaries, breaking barriers, and achieving the extraordinary.
Otaku 2.0 sought to redefine the way we approach testing, celebrate the spirit of innovation, and pave the way for a brighter future in tech.