TestVagrant

How to Automate Workflow-Based Applications using Workflow Design Pattern?

Blog

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

  1. Successful Payment
  2. Failed Payment

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.

  • A workflow pattern can turn customer journies of the application into reusable workflows.
  • Page Object Model provides an abstraction for a page (a webpage or mobile screen). The workflow pattern could then be leveraged on top of POM to build workflows that represent a user journey
    Let us look at a sample workflow:

Let us look at a sample workflow:

  1. Select Product
  2. Add to Cart
  3. View Cart
  4. Checkout
  5. Login
  6. Fill Contact Information
  7. Continue to Payment

A workflow pattern has below components

  • UseCase: Acts as a data structure to store test data for the current test and the current state of the application.
				
					@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> Q getData(Class<Q> 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=
            ()-&gt;{
        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));
    }
}
				
			
  • Workflow Doc: A workflow doc is the blueprint of your customer journey. It ties workflow definitions to construct end-to-end customer journies.
				
					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();
    }
}
				
			
  • Workflow Test: By using the Workflow and the UseCase, we can reuse the workflow and assert wherever we require.
				
					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,
  • Happy Path Case
    Negative Case
    Boundary Cases
    becomes a cakewalk. However, this pattern has some cons too…

Cons of workflow:

  • We can’t apply when the workflow is dynamic, we can apply when the application is completely evolved and the workflow is stable.
  • Since the workflow definition is tightly coupled, we can’t make major changes in the definitions.
  • 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

Resources:

Other Related Articles

Scroll to Top