Going further with our research on different design patterns used on automation testing, we’ve made a stop around the state pattern. As any developer knows, this one is part of the behavioral family.
For testers maybe it’s a new concept, so as a short notice, behavioral patterns are responsible for setting up a communication between objects.
State pattern provides different behaviors based on the internal object state. In this article, our purpose is to find an applicability in test automation. So what about the life cycle of a test? A simple scenario would be:
- Start the test
- Execute different steps
- Stop the test
We’ve just listed three states, of course, that there could be more but to get a simple understanding of the state pattern that’s enough. How we accomplish this? Just follow the implementation.
- Python 3.6
- Selenium Webdriver
Step 1 – State manager
First, we need a manager. This acts as an interface to the client and provides the actual state of the object.
class Manager: """ State machine manager. Acting as an interface to the client and providing the actual state of the object """ def __init__(self, state): """ :param state: current object state """ self._state = state def get_state(self): """ :return: state getter """ self._state.run()
Step 2 State interface
Secondly, there is a state interface, and
get_statemethod will be implemented by each sub-class (reflecting specific states)
class State(metaclass=abc.ABCMeta): """ Interface definition for behaviour encapsulation """ def __init__(self): self._driver = get_selenium_driver('chrome') def get_driver(self): return self._driver @abc.abstractmethod def run(self): pass
Step 3 – Actual states
Going further, you can find three states implemented.
class StartTest(State): """ Prepare the test execution environment """ def run(self): print(" Start test state!!! ") self.get_driver().get('https://en.wikipedia.org/') class ExecuteTest(State): """ Run run different test steps """ SEARCH_BUTTON = 'searchButton' def run(self): print(" Execute test steps state!!! ") if self.get_driver().find_element_by_id(ExecuteTest.SEARCH_BUTTON).is_displayed(): print("Search button available") self._driver.find_element_by_id(ExecuteTest.SEARCH_BUTTON).click() else: print("Search button not available") class StopTest(State): """ Close the testing session """ def run(self): print(" Stop test state!!! ") self.get_driver().quit()
Step 4 – Testing
How all those works together? First, we instantiate the states which we want to have for our test (start, execute and stop), next we will pass the instance to the manager, so in the end, it will get each state for our object.
if __name__ == '__main__': start = StartTest() execute = ExecuteTest() stop = StopTest() for test_state in [start, execute, stop]: manager = Manager(test_state) manager.get_state()
The output will be:
Start test state!!! Execute test steps state!!! Search button available Stop test state!!!
Using state design patterns facilitates the test case lifecycle definition, having each state clearly separated, but controlled at the same time by a manager. The only downside which we’ve noticed here is the necessity to instantiate the manager each time when we want to create a new state. The applicability could be also extended like adding different dependencies between the states so we will leave it up to your imagination to come with those scenarios.
More details about the implementation could be found here