
Confession, this is the album where I stopped listening to REM. Dunno why, because Monster was one of the best albums I never bought.
Let us review our strategy, especially referencing our usage of White. I find it interesting that Test Complete makes it so hard to “do the right thing” as propounded by both Scripting GUI Tests in Ruby and by White. And just what is that?
The responsibility of the test script is to
- perform a series of actions and verifications.
- allow someone to read the script and understand what the hell is going on.
The first is a gimme. The second is interesting. When I have my developer hat on, I believe code is read more often then it is written. Wearing my manual tester hat, I believe the scripts should be clear and show intent. Tricky scripts are trouble. They are also a pain to maintain. So, if automated testing the meeting of these two worlds, the clarity and intent of the scripts must be sacrosanct. The libraries the scripts use, on the other hand, just need to be treated like code and kept maintainable.
What do I want my test scripts to look like? There are two options, DSL, or clearly abstracted test code.
First, lets look at DSLs, Baby! WAHOOO! Tests that double as manual tests!
open sekrit with New Document insert image "picture.png" into sheet first image item should match stored image "stored_0001.png"
I could do that. I could use some functional programming language. I could also use something like cucumber and code behind my feature files. But what’s the point? All I need is clarity, so the auditors / business people understand. I LIKE dsls, but this buys us nothing right now.
Option two uses Objects to clarify the application.
sek = Sekrit.open( :New_Document ) doc = sek.document doc.add_image("picure.png") Assert.equal doc.images[0].as_bitmap, Stored.images["0001.png"]
This is at the proper level of abstraction for a slightly motivated business user to understand. What they don’t care about at this level is the control hierarchy and control manipulation. Unless the requirements say, “via a popup menu” or some such.
If you’re going to be technical, this is a facade. It’s a wrapper to the complex underbelly of the white framework. It allows for a higher level use of the collected objects and re-use as we don’t have to repeat the code for complex actions and interactions. It also decouples our tests from changes in our application. I’ve had to chase that down in our current automation set, and I hate it.
How do we build up this abstraction? To be compatible with White’s recommended practices, I’m going to call this facade a “Screen”. We’re going to write a test for an application for which we have no source. Download blu and warm up your tweetin’ fingers. We’re going to log in!
# our screens class Blu def initialize(application, window) @app = application @win = window end def login_screen return LoginScreen.new(@app, @win) end end class LoginScreen def initialize(application, window) @app = application @win = window end def login_user( username, password, remember = false) @win.get_textbox("UsernameTextBox").enter username @win.get_textbox("PasswordTextBox").enter password rem = @win.get_checkbox("Rememberage") if rem.checked != remember rem.enter " " end @win.get_button("LoginButton").click end end
You noticed that the BluScreen has a LoginScreen? Our application has regions and concepts to which we wish to refer. For example, we may want to represent a ribbon control, a toolbar, different types of documents, or even the clipboard.
How do we use these screens? Let’s flesh out this little story in Dan North “Story” format.
As I malicious user, so I can't defame Brian's good twitter name, Given blu is open And no one is logged in When I attempt to log in using a bad password Then I should see a warning that my login information was incorrect
app = Application.attach("blu") win = app.get_window("blu") blu = Blu.new(app, win) login = blu.login_screen login.login_user( "Myotherpants", "NoWai!" ) # A test would have assertion here...
I’m holding out on you. The truth of the matter is, I started with the test steps first, then made screens that abstracted those steps. But there’s magic in the screens, they have magic strings passed around like @win.get_checkbox("Rememberage")
. Where did that come from? I got it from UISpy. I’ve read about Woodstock, Snoop, and a few other tools, but UISpy is the only tool I used for this post. It is in the Windows SDK. UISpy shows a tree of controls. When you click on one, it hilights it with a red box. It also displays information such as the automation Id, those magic strings we have scattered in our code. Before my two picture tutorial on UISpy, let me note, those strings are what’s likely to break your script in the future as your application grows.
what else is there?
These aren’t tests, they are automation. I’ll eventually wrap the automation with RSpec and get some real tests. I’ll get to Cucumber. I’ll publish examples. I promise.
But next, I want to address three types of custom controls:
User controls, Inhereited and Templated Controls, and Framework Elements. Another problem is finding un-named controls. I’ll see you next week.
One last bit, here’s what the white ruby library is starting to look like.
white_loc = (File.dirname(__FILE__) + "\\White_Bin\\") $LOAD_PATH.push(white_loc) require "White.Core.dll" Application = Core::Application Button = Core::UIItems::Button CheckBox = Core::UIItems::CheckBox TextBox = Core::UIItems::TextBox Window = Core::UIItems::WindowItems::Window SearchCriteria = Core::UIItems::Finders::SearchCriteria class Window def get_button(*args) self.method(:get).of(Button).call(*args) end def get_button_labeled(label) self.get_button(SearchCriteria.by_text(label)) end def get_checkbox(*args) self.method(:get).of(CheckBox).call(*args) end def get_textbox(*args) self.method(:get).of(TextBox).call(*args) end end
