Arranging the playing cards in a deck to be in one’s favor is called “stacking the deck.” Outside of card playing, people use the term more generally to mean arranging a situation to increase your chances of a favorable outcome.
When it comes to test automation, the meaning is no different. Specifically, you want to arrange your architecture, implementation, and usage patterns to be appropriate for the entirety of your application’s desired life span.
One approach to future-proofing is to focus less on your automation framework and more on the automation stack. An automation stack is a layered automation architecture where each layer builds upon the previous one and provides an audience-appropriate interface to the lower levels’ capabilities. This layered approach helps extend an implementation’s longevity by increasing the portability of the implementation across frameworks and across tools.
Here’s how layers can be a valuable part of an automation implementation, and some caveats to consider before layering an architecture.
Understand two basic concepts
When building an automation stack, recognize that you are programming; you are creating software and need to treat this activity as a software development activity. This means you must follow software development and delivery practices that are appropriate for the software you are developing.
Two powerful development principles for creating an automation stack are encapsulation and abstraction.
Hiding details via encapsulation
Encapsulation is the practice of hiding the implementation and data details of a software construct. This hiding is valuable because it lets a developer change the implementation, the data structures, or both without causing the user to make undue code changes.
Encapsulated code allows you to fix bugs, enhance performance, and add capabilities as needed. Care must be taken, however, to sufficiently preserve the existing behavior of the encapsulation such that existing users of that encapsulation are not adversely affected and do not need to modify existing code.
As an example of encapsulation, consider the basic data structure called a queue. Suppose you implement this queue using a linked list, which is a suitable queue implementation in many cases. Suppose, however, that your requirements change such that you need to add a feature to your queue that allows searching from the end of the queue.
Linked lists, by default, are singly linked lists, which means a search from the end of the list is inefficient, especially for very long lists or if your list item comparisons are time-consuming. A data structure that is more efficient to search from the end is a doubly linked list.
An encapsulated implementation means that a change to a doubly linked list should have no impact on existing users of the queue implementation, but does allow for a more efficient reverse search.
Moving attributes to a general structure
Abstraction is the practice of moving concrete attributes of data structures to a more general data structure; this more general structure can then be used when defining the specific data structures.
Abstraction is best explained via a classic example based on animals.
Dogs, nutria, and platypuses all have several things in common: They have some number of legs, they can hear and see, etc. You can define those common traits, those attributes, into a data structure that is shared by all of those and other animals’ data structures.
One possible name for this abstraction could be “animals that have legs”; for the sake of programming brevity, you could call this data structure LeggedMammals or any other name that makes sense in your domain.
The specific animals (dogs, nutria, and platypuses) can then add their specific implementation of the attributes or retain the shared implementation using the mechanism that is appropriate to your design and programming paradigms. The abstracted implementation allows a single place for common attributes to be defined, and allows for a more generic interface when handling an arbitrary LeggedMammal.
[ Special Coverage: STPCon Fall Boston 2019 ]
Layering it up
Applying encapsulation and abstraction to your automation implementation allows you to organize your automation components into layers—discrete partitions of well-defined capability.
Further, you can construct these layers such that each layer builds upon the capabilities of the previous one; this is how you create your automation stack. The figure below shows the conceptual model of this stack.
Figure 1. Automation stack conceptual model.
Each layer has a specific purpose.
The raw tools layer
This is self-explanatory; it’s the layer that represents the actual tools that you use to drive your system under test (SUT). Some examples of raw tools are Selenium, Watir, and REST Assured. Raw tools may also be APIs that drive third-party hardware and embedded hardware, which can be either vendor-supplied or proprietary.
The abstraction and encapsulation layer
This wraps the raw tools; the raw tools layer is not accessed by any layer apart from the abstraction and encapsulation layer. This layer supplements the raw tools layer’s native capabilities and future-proofs it against implementation changes, including possible changes in the raw tool that’s used. It can also protect against changes in the raw tools that may adversely affect the higher layers.
The actions layer
This provides domain-specific atomic and near-atomic actions that directly correspond to actions required to manipulate the system under test.
For example, if you are automating browser interactions, an action might be “find the login link and click it,” which can result in a programmatic call such as: browser.LoginLink.Click()
If you are automating a REST endpoint, an action might be “perform a GET to retrieve the user phone number,” which can result in a programmatic call such as: endpoint.GetPhoneNumber(UserData)
The behavior layer
In general, this contains grouped sequences of actions and other behaviors. These sequences are the steps that a user of the system under test would perform as part of that user behavior.
One example is a behavior logging into a system. This behavior might have the steps of:
- Click the login link
- Enter username
- Enter password
- Click the enter button and
- Verify successful login
The resulting programmatic call could be:
The scripts layer
These are the test scripts that are traditionally associated with the automation of testing activities. Test scripts call actions and behaviors to automate those activities.
Benefits, and a caveat
The benefits of this layered approach are the same ones that application developers obtain from applying these same basic software development principles. These benefits include future-proofing against implementation changes in the encapsulations, having a single or very few places to modify a specific algorithm or flow, and code reuse.
Each of these benefits helps reduce the cost of maintenance, which is where the brunt of an automation implementation’s effort is expended.
Implementing an automation stack also gives you portability across frameworks. If you do not bind the lower parts of your automation stack to a specific framework, your stack can be used across multiple frameworks.
This gives the framework’s users more options when choosing an appropriate implementation for automation. The lack of binding allows you to use your stack outside of traditional, test case-based automation as well. The stacks we use at Magenic are built in this fashion.
Since nothing is perfect, there’s one big caveat. Building software in this manner requires you to invest time and effort up front; if you do not, you run the risk of creating an unmaintainable morass of code. If you require a quick win or need to show results “soon,” you should try to narrow your stack’s focus or pursue a different approach, at least initially.
Using the layered model
It is important to note that the stack approach is not a GUI-only approach; this has been a common misconception. If you review the examples provided for each layer of the conceptual model above, you see both GUI and non-GUI examples.
The model presented here has evolved over the last 20 years. It’s been used for automation in telecom, e-commerce, healthcare, fintech, and manufacturing; it has proved conceptually sound in each of those models.
That said, this model is not meant to be prescriptive. I encourage you to study the model, use it where it’s appropriate, and modify it to make it more appropriate for your own purposes.
Like this? Catch me at an upcoming event!