This post is the fourth in my series regarding approaching testing when you are using an executable specification type tool. The first, second and third posts are pretty much necessary reading for this one to make any sense to you at all.
Getting back to testing the application, one thing I haven’t done yet is actually create the user. Given that this is the point of the @starting scenario, this seems like a glaring flaw in my design. Yet, keep in mind what we did up to this point: we validated that this starting point for the scenario was the right place to be starting. It might help to recap a bit why that was.
This application — as with many applications — requires a user to be created in order to do certain things. These users will then perform actions that they have permission to do. For example, a user can create a product with my application. A user can also create a study and associate that study with a product. In fact, you’ll notice that my @starting scenario has been using a “When” clause up to this point. That implies an action being taken: in this case, creating a certain type of user. In the back of my mind, however, I realize that this will easily become a pre-condition for other tests. I’ll revisit this idea when we get further along. I just wanted to plant the seed now.
Getting back to work, in order to create the user, I have to get to the form. I recommend going through the steps manually — as I would recommend before doing any automation. If you play around with the sample application, you’ll see you need to do these actions:
- On the Users page, click the “Add New User” link.
- On the Create User page:
- Enter a login name.
- Enter a first name.
- Enter a last name.
- Enter a maximum level.
- Click “Set Password” link.
- Enter a password.
- Enter the password again to confirm.
- Click “Save Current Information” link.
- Select a role.
- Click “Create User” button.
That’s a lot of steps and I purposely made a few things seem a little more “difficult” to get to than you might expect. That said: this rough format does mirror an actual application I worked on. In that real application, the step of clicking on the “Save Current Information” link was actually a two step save process.
So now I’ll add some logic to do all that to my existing step. Here it is in full:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
When (/^I create a "([^"]*)" user$/) do |role| step %{I am logged in as an administrator} step %{I am on the users page} click_link("Add New User") @current_user = "autotester_#{(0..8).to_a.map{|a| rand(8).to_s(8)}.join}" fill_in("loginName", :with => @current_user) fill_in("firstName", :with => "auto") fill_in("lastName", :with => "tester") select("Advanced", :from => 'level') click_link("Set Password") fill_in("password", :with => "testing") fill_in("confirmPassword", :with => "testing") click_link("Save Current Information") select(role, :from => 'role') click_button("btnSubmit") page.should have_content(@current_user) end |
This works — try it if you’re following along! — and will do the trick but it puts a lot of logic in the step. I’d prefer for that logic to be called from the step as opposed to executed within it.
As I look at my logic, I’ve really got a set of things I’m doing there:
- I’m creating the basic information for a user.
- I’m setting the password for the user.
- I’m saving the current information.
- I’m selecting a user role.
- I’m creating the user.
- I’m verifying that the user is now in the system.
Also potentially lost in the mix is that I’m generating a unique ID of sorts for each user that’s created. In the first post, I had said I had no plans to rely on any sort of “gold” data, meaning that test steps would create the data they need on the fly. To make sure there are no conflicts, I create a unique ID as part of the user name. While this user name may seem cumbersome, keep in mind that’s only the automation that needs to reference it. Looking at the logic you can see that whatever name is generated is stored in @current_user instance variable.
But, as I said, all of this logic makes the step pretty heavy in terms of implementation. I might want to use some of these actions in different steps. What I really need is something that helps the method figure out how to do the what that the method specifies. Well, I already created a helpers.rb file (stored in specs/engine) in a previous post. Within that file, I’ll create another module:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
module AppHelper def generate_id (0..8).to_a.map{|a| rand(8).to_s(8)}.join end def set_password_for_user click_link("Set Password") fill_in("password", :with => "testing") fill_in("confirmPassword", :with => "testing") end def save_the_user click_button("btnSubmit") end def verify_user_save page.should have_content(@current_user) end def create_user click_link("Add New User") @current_user = "autotester_#{generate_id}" fill_in("loginName", :with => @current_user) fill_in("firstName", :with => "auto") fill_in("lastName", :with => "tester") select("Advanced", :from => 'level') end def set_role_for_user(role) click_link("Save Current Information") select(role, :from => 'role') end end |
That may seem daunting but it you look, all I did was distribute the logic I had in my step over a series of methods. All of that logic there is just the code from the test step. Then, as with my previous helper module, WithinHelper, I have to add my new module to the World:
1 |
World(AppHelper) |
Now I can refactor my test step to call the above logic:
1 2 3 4 5 6 7 8 9 |
When (/^I create a "([^"]*)" user$/) do |role| step %{I am logged in as an administrator} step %{I am on the users page} create_user set_password_for_user set_role_for_user(role) save_the_user verify_user_save end |
If you were to run the cucumber command again, you should find that the test still works in terms of creating a new user in the system.
If you want to see that users are being created in the system, load up the Sinatra application and navigate to the Users page. You will see a list called Current Users that will display whatever you have entered. This information is being stored in a small file-based database that I create as part of my application. The database is a file called app.db
At this point, note how all of the user details were handled behind the scenes. This is because this user data condition is largely irrelevant to me in the details except for the fact that it’s a user with a Clinical Administrator role. I don’t really care what the name is and essentially I’m creating what is the equivalent of a “default” user for me — and thus for my tests.
Does this make sense? Does it make sense to just have data created behind the scenes like this? Sometimes, yes. This is the basis behind tests that specify their data conditions and then follow a setup pattern to create the data they need as they need it. In the next post, which I think will be the last of this series for now, I’ll talk a little about an alternative to the “behind the scenes” scenario I described in this post.
Beyond finally creating a user the main thing I hope you see here is how I really capitalized on the idea of a helper module to utilize functionality outside of my step definitions. Not only does this mean that this functionality can be reused in other step definitions but also means I’ve now insulated my low-level implementation changes to those methods. Not only will it be unnecessary for the test spec to change, but the step definitions should largely remain immune to low-level changes as well.
While this is easy to say, I have no doubt that someone could point out areas of fragility in my logic. Further, the ability to insulate implementation changes from the test writing does depend very much on how close to the implementation your test specifications are written. The closer you write to the domain — the what — without including navigation or execution details — the how — the better you can insulate yourself from changes. That being said, there are trade offs to always writing from a high-level domain point of view, not least of which is that people can spend a lot of time trying to figure out just the “right” way to word something.
Anyway, as I said, probably just one more post in this series coming up.