Testing a State Machine with ODT

Almost as a rule, when developing automated tests for an application, we are called to provide the most coverage for the least effort. Often, this takes the form of the "broad stroke" in functional testing; i.e. trying to write simple automation that can cover all the forms of an application, making sure that all the standard and common buttons work, all the forms appear at the right position, etc. There is nothing wrong with this, and in fact, it can be a best practice (depending on your application) to first provide automation for some of the most basic UI functionality of the application under test, providing your team with some peace of mind and making sure that your releases always work at the most visual level. In this article, we will take this principle just a bit farther by covering the functionality of a common state machine.

Whether your application has one data entry form or hundreds, there is always a concept of "state", or what's enabled while other things are disabled? The code that handles the management of these various control states is called a state machine. Imagine the following common scenario. Our hypothetical application under test (AUT) has several data entry forms, each of which contains five standard controls: Add, Edit, Delete, Save and Cancel.

Figure 1 - all controls.


Let us further assume that our AUT has three basic user states, Viewing, Adding (or Inserting) and Editing (obviously, there would be more, but we'll give way to simplicity here). You can no doubt see that the user's available actions are dependant upon the state of the application. If we are in a Viewing state, the user can Add, Edit and Delete, but cannot Save or Cancel because nothing has changed that would cause the need to save or cancel. Therefore, we can assume that for our three basic states, the following applies:

Viewing:

Control Enabled
Add True
Edit True
Delete True
Save False
Cancel False

the Viewing state

Figure 2 - the Viewing state.

Adding:

Control Enabled
Add False
Edit False
Delete False
Save True
Cancel True
the Adding state

Figure 3 - the Adding state.

Editing:

Control Enabled
Add False
Edit False
Delete True
Save True
Cancel True
the Editing state

Figure 4 - the Editing state.

I made a command decision concerning the state of Delete: I decided to make it unavailable for Adding (because we have not yet added, and can simply Cancel if we wish to change our minds) while it is available for Editing (because our item does exist while we edit it, and we may wish to simply do away with the item altogether while editing it. It infuriates me to have to change back to some state (back to Viewing) in order to take some other action (Delete)).

Because our hypothetical AUT will have multiple forms which need to be verified in their handling of state, we will build an ODT (object-driven testing) class in TestComplete 4 which is able to verify functionality of the tested state machine in multiple forms. Note that if you do not have TestComplete 4, you'll still be able to build the same class in version 3, although some of the script code may vary since my demo AUT is built in C# and version 4 of TC handles .net applications a bit differently.

Tutorial

Please note, as we begin this tutorial, that this is not a follow-along-exactly-step-by-step kind of tutorial; if you did that, at the end of the steps you'd have a test project that tests the state machine of my demo app very nicely. Not exactly what you're after. Instead, the idea here is to follow along generally, tweaking the projects so as to match your specific needs. In addition, let me just say that this test project and certainly the demo application, contain short-cuts and non-optimized code; primarily because I wanted to spend my time on the discussion of the principles, rather than on the demo code.

Note that I'm developing my test project in TestComplete 4 (it's in beta while I write this), however in the interest of supporting those who are using TestComplete 3, I've chosen to work with the TestComplete 3 compatibility plug-in enabled. Thus, most of my test project code should work just fine in TestComplete 3. Those working in TestComplete 4 will need to either enable the compatibility plug-in, or to alter the code to use TestComplete 4 calls.

Our tests will consist of an ODT testing class which has the name of a form (so that it can test that form, and so that it can test multiple forms) and the names of the various state controls. The state control name properties will default to a standard set of values (which you can alter) so that if most of our forms adhere to a naming standard, we won't have to populate those values. In addition, the ODT class will have methods which can validate a form, clicking the various controls to set the state, and then to verify the condition of the controls in those states.

Finally, a note about visibility, or scope: ODT does not provide a true notion of visibilities in class definition. Everything is of a public scope. However, when I'm defining classes, I still want the benefits of public and private class members. Thus, I achieve an emulation of scope using naming prefixes. My test developers know not to call methods with a certain prefix in their own test code - those are "private" methods and are called only by the ODT objects themselves. My methods are prefixed with "pr_" for "private" and private properties are prefixed with "F" (for "field" - showing my Delphi bias here).

In TestComplete, create a new project - I've named mine "StateMachine" contained within a project inventively named "StateMachine". Since we're using ODT for our testing, you can use the Object-Driven Testing project template. I also have a C# demo application (named StateMachine01) which I'm using for the demonstration. I'll make the source code available for download. I've named my main unit "Main" and created a new unit called "odtStateMachineImpl". I've added the demo application to the TestedApps node of the project and now my project is ready to begin development.

the TC4 project

Figure 5 - the test project in place.

Defining the Class


First, create an ODT class. If you're unfamiliar with the process of creating ODT classes, you should examine that topic in the TestComplete help.

I've named my class "odtStateMachine".

The class has the following properties defined:
Property Default value
AddControlName btnAdd
EditControlName btnEdit
DeleteControlName btnDelete
SaveControlName btnSave
CancelControlName btnCancel
FormName


The default values are based on the names of the controls in my demo application; change these to whatever your application uses.

For the methods, I have the following methods defined. I'll include the Script Procedure name here even though yours may vary.
Method Script Procedure Name
Validate odtStateMachineImpl.Validate
pr_ClickAdd odtStateMachineImpl.ClickAdd
pr_ClickEdit odtStateMachineImpl.ClickEdit
pr_ClickDelete odtStateMachineImpl.ClickDelete
pr_ClickSave odtStateMachineImpl.ClickSave
pr_ClickCancel odtStateMachineImpl.ClickCancel
pr_CheckStateAdd odtStateMachineImpl.CheckStateAdd
pr_CheckStateEdit odtStateMachineImpl.CheckStateEdit
pr_CheckStateView odtStateMachineImpl.CheckStateView



To use this class, one would create it, populate the ControlName properties (if needed) provide a value for FormName and call Validate. Validate uses all the "private" methods to click the buttons and verify the states. The source code for the odtStateMachineImpl unit follows (it contains all the code for the ODT class).

Implementing the class


This code is where you'll need to tweak to make it work specifically for your application. You'll want to examine all the routines, but in particular Validate, wherein I click all the various buttons in a variety of sequences so as to check that the states are properly displayed from each sequence. Also CompareControl, where I am only checking for the enabled state of a control, but you could also add the verification of caption, position, etc.

Since the ODT classes uses string values representing the names of the controls under test, we make liberal use of the WaitChild method, which lets us get access to a child object via its name. Take notice of the GetControlByName routine which first gets the tested form then returns the specified control. Note that since this sample code is in Jscript, I've avoided the problems of passing by reference by checking the control's Exists value outside the GetControlByName function, instead of returning both the control and a Boolean value for success or failure. Feel free to improve the code where you wish.

Using the Class


In the Main routine of the sample test project, I have done the following:
I am able to accept the default state control names in my ODT object because my tested form names all its controls as we expect. If I needed to test a form that did not adhere to my naming standards, I could create the ODT, then populate the state control names as needed, before calling Validate.

Note that I call lObj.Validate twice because my demo application has an option to work with errors or to work correctly. We run the test once with errors, uncheck the chkSabotage check box and re-run the tests.

Conclusion


Note that the idea here is that we want to be able to plug many forms into this test class (at least any forms that implement a state machine and somewhat adhere to a set of standards). Thus we can use our state machine tester to cover all our state machine forms and check their functioning all with a minimal amount of code. We might iterate through our application's forms, opening, and running the test class and then moving on to the next form. Or we could have our state machine tester as a part of each suite of tests for a form: open the form, check its state machine, then run all its other tests. Open the next form…

Huzzah to test code reuse!

Close

By submitting this form, you agree to our
Terms of Use and Privacy Policy

Thanks for Subscribing

Keep an eye on your inbox for more great content.

Continue Reading

Add a little SmartBear to your life

Stay on top of your Software game with the latest developer tips, best practices and news, delivered straight to your inbox