- Workflow Test: By using the Workflow and the UseCase, we can reuse the workflow and assert wherever we require.
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?
“The best designers will use many design patterns that dovetail and intertwine to produce a greater whole” By Erich Gamma who is one of the co-authors of the software engineering textbook, Design Patterns: Elements of Reusable Object-Oriented Software
Consider an E-commerce website, which has several pages and many common workflows.
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
2. Strategy: Let’s define a family of algorithms put each of them into a separate class, and make their objects interchangeable.
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());
}
}
If you now read the test, a single workflow document can be used to either navigate up to log in or till payment. This helps avoid a lot of code duplication and verbosity but also helps accelerate Test Case Development.
Once a workflow is agreed and defined, writing test cases such as,
becomes a cakewalk. However, this pattern has some cons too…
Cons of workflow:
Design patterns are important in the software industry because they provide us with a way to solve issues related to software development using a proven solution. Second, design patterns make communication between designers more efficient and help make code more readable and reusable.
The full code can be accessed here
Thanks for reading
Share This Article
“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.