Setting up test stages with State Machine design

Sometimes debugging an automated test case is problematic, specifically when the test case has many stages built into it. Creating isolated stages can lead to a better identification of the issue. To address this problem we can use a design pattern called State Machine.

What is state machine pattern?

A state machine is an environment that manages the data stored about an object in a specific part of the execution. Each state is selected based on the given input from a previous state and dictates the outcome based on the logic it performs, sending the output to the next state.

Defining our generic test steps

In this part, we define our TestCase machine class. This will keep a record of all the states but does not know the specific order in which to execute. That information is stored inside each state class.

Let us take a basic test scenario where we want to execute logic before the test case, then run the test, perform some operations after, and in the end report the results. For this, out test machine class will look like this:

class TestMachine(object):
    before = None
    run = None
    after = None
    report = None

    @staticmethod
    def run_all():
        current_state = TestMachine.before
        current_state.run()
        while current_state.next() is not None:
            current_state = current_state.next()
            current_state.run()

Notice here that we used static definitions for our TestMachine class. This means that class can, and must, be reused each time when executing or defining a new test case.

Creating the specific State classes

Since we have defined four states in out TestMachine class, it falls in order to define 4 states.

The states have been designed in order to be reused with different logic, which is injected in their run methods. The more specific detail is the pointer to the next state in the execution process.

class RunTestState(State):
    def __init__(self, run_def):
        super().__init__()
        self._run = run_def

    def run(self):
        self._run()

    def next(self):
        return TestMachine.after


class BeforeState(State):
    def __init__(self, run_def):
       ...
    def next(self):
        return TestMachine.run


class AfterState(State):
    def __init__(self, run_def):
       ...
    def next(self):
        return TestMachine.report


class ReportState(State):
    def __init__(self, run_def):
       ...
    def next(self):
        return None

In this way, we have defined the link between the states: the execution starts with the BeforeState, which jumps from state to state using the next() method until it gets to the ReportState which has no successor.

Injecting test logic for each state

Now that we have modeled the behavior of our state system, the next step is to set the logic of our states. The way the states have been defined allows for functions to be passed as parameters in order to fully customize the execution logic. As a consequence, the states can be reused by just changing the function with is injected into them.

For the sake of exemplifying we will inject functions with simple logic, like a print statement.

def before_def():
    print("before")

def run_def():
    print("running")

def after_def():
    print("after")

def report_def():
    print("report")

How all comes together

The final step is to link this all together. For this, we will use the TestMachine’s static attributes in order to set the corresponding value for a test:


TestMachine.before = BeforeState(before_def)
TestMachine.run = RunTestState(run_def)
TestMachine.after = AfterState(after_def)
TestMachine.report = ReportState(report_def)

TestMachine.run_all()

The method definitions and the test run is specific for a single test, in order to prepare for a new test we have to change the methods that hold the logic. This leads to a better test step modularization and helps to define specific methods that perform specific operations.

One thought on “Setting up test stages with State Machine design

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.