When you're trying to perform automation on an application, if there's only certain end-points that you're checking (with many intermediate states that you need to visit), it may be helpful to represent the application as a state graph. That way, you can shorten the amount of code that's required for moving between end states. In this post, we're going to explore why that idea may be useful - as well as go through a small sample demonstrating the approach.
There's one assumption that this post makes:
- You've decided to use a series of helper classes that correspond to pages (states) on your application - and methods to perform actions on each of those pages.
Normally, when you're creating an automation framework, you may decide to have a series of classes for each of the different pages on the application under test. Those classes each would have methods that are only valid from that page and go to other pages.
class Library: - open_book(string book) - open_leftnav(void) class Book: - turn_page_forward(void) - turn_page_backward(void) - open_book_menu(void) - highlight_text(coord top_left, coord bottom_right) class BookMenu: - go_to_library(void) - close_menu(void) class HighlightMenu: - select_options(void) - select_colors(void) - close(void) class OptionsMenu: - select_bold(void) - select_italics(void)
Based on these classes, you may have a bunch of helper classes inside of your test fixture, and you could create a test like the following:
def bold_text_test(options): self.library_helper.open_book(options.book_name) self.book_menu_helper.close_menu() self.book_helper.highlight_text(options.highlight.top_coord, options.highlight.bottom_coord) self.highlight_menu_helper.select_options() self.options_menu_helper.select_bold() self.highlight_menu_helper.close() baseline = get_baseline(options) picture = take_picture() assert.compare(baseline, picture)
Here, the portion of state between the start and reaching the options menu are all intermediate steps. If we were to model the states as nodes in a graph, we could traverse the shortest path using bread-first search, along with some metadata.
This helps reduce the number of instructions that would be required to create the previous test.
def bold_text_text(start, end, metadata, baseline): graph_traversal.traverse(start, end, metadata) baseline = get_baseline(baseline) picture = take_picture() assert.compare(baseline, picture)
The downside of this approach is that with sufficiently large applications, it becomes more and more difficult to keep track of the metadata (such as book name) required to perform the traversal. And it's not obvious which portion of the metadata is associated with what actions.