Using Lucid in Context, Part 1

Here I’m assuming you have followed the previous post and have a project set up and ready to go. This post will take you through using Lucid with Fluent and also talk a little bit about how you write your TDL and some of the considerations that go into that. This will be a fairly comprehensive post, attacking along a few different lines of thought.

The Basis of Lucid

Proponents of tools like Cucumber often say that “it’s not a testing tool.” Well, I believe that testing is a design activity. Therefore, for me, Lucid is a testing tool. But it’s not just a testing tool. By itself Lucid will never even touch your application. Instead, Lucid will delegate that work off to a human or to an automated testing tool. Lucid is really about using tests as a communication mechanism and promoting the idea I just stated: that testing is a design activity.

One of the guiding principles here being that if you make a genuine effort to write requirements-as-tests (i.e., test specifications) and if those artifacts work as documentation for technical and non-technical stakeholders on your team or user base, you’ll find that you are talking with those stakeholders and users about what “acceptable behavior” means for the application. You will begin to build up a shared notion of quality. And you will be doing all this with a shared language that matches the business domain. Those conversations will reveal insights about their understanding of the problems that need to be solved as well as the possible solutions that can solve them.

For those of you that like to say you “follow ATDD”, well, this is how the “acceptance” part of that fits in. Acceptance testing is built in at the front, not tacked on at the end. But note that when I say “acceptance testing”, the discussions and collaborations I just mentioned are testing.

From Lucid’s perspective, the executable tests are in some ways epiphenomenal; the real value lies in the knowledge you discover during those conversations and how the test specs can become living documentation. Just as the tests themselves are epiphenomenal, so too are the tools used to execute those tests. Lucid simply acts to make sure there are guidelines in place for writing tests (encapsulated in a Test Description Language) and that those guidelines allow the tests to be executed both manually and/or via an automated testing solution.

So with that bit of context in mind, let’s get started doing some work.

First, make sure you grab the Triangle Web app that was covered in Using Lucid on Web Apps. There are directions in that post for how to run the file, but basically just make sure you have Sinatra:

$ gem install sinatra

You can run the application with this:

$ ruby triangle_web_app.rb

Then you can browse to http://localhost:4567/ to see the app in action. You might want to get a feel for the application, just by playing around with it for a bit. It’s not that complicated and its operation should be pretty clear.

Also make sure you have generated a project as detailed in the last post with this command:

$ lucid-gen project tutorial-web

You are ready to go!

Test Spec and Test Matchers

First create a file in your specs directory called triangle_fail_test.spec. Put the following in that file:

Feature: Parsing Triangle Test Results

  As a test evaluator
  I need the app to indicate a score of zero
  so that I can determine if a candidate has tried no conditions
  
  Scenario: No Conditions Tried
    Given the triangle test app
    When  no conditions are tried
    Then  the user receives a score of "0"

That’s all pretty standard stuff. We have a specific feature (“Parsing Triangle Test Results”), we have a feature injection template saying what we are testing in this spec file, and then we have a scenario delineating a particular test and data condition (“no conditions tried”).

If you want to try this out in the sample application, this would be akin to calling up the app and immediately clicking the ‘Evaluate Data Conditions’ button.

The Test Description Language

Keep in mind that what you put in the spec file is your TDL — your Test Description Language. The Given/When/Then are structuring elements provided by the Gherkin API. But everything else is whatever domain language you wish to use to describe the feature, ability, or business need that you are specifying.

It’s important to be clear on the concept of a TDL. For those of you who have been in the industry awhile, you’ll probably remember the concept of PLanguage (popularized mostly by Tom Gilb) that was part of Competitive Engineering. The idea of a TDL is sort of an outgrowth of that, applied to behavior-driven development and domain-driven design. Most everyone reading this has probably heard of design patterns in relation to programming, right? Well, TDL is one of the ways to apply design patterns to tests. In fact, a TDL is a test pattern.

Modern test design and writing is becoming predicated upon the idea of a what people like to call a “business readable DSL.” This is the case whether or not you ever consider introducing automated testing into the mix. (Don’t get hung up on the term “DSL” and think of it only in a programmatic context.) The idea of “business readable” means that your tests are written in a way that, at a certain level of approximation, the tests are the requirements. Or the requirements are the tests, if you prefer. (See Tests and Requirements, Requirements and Tests: A Möbius Strip.)

All of that is easy to say. It’s not always easy to do. So before you even consider a TDL as part of your work process, you should first embark on understanding how everyone can work together to bridge creative, innovative, exploratory test thinking with the need to also have tests that are scripted, that can be effectively referenced, and that can be efficiently maintained.

As with all such similar tools, Lucid wants you match up your domain phrases with matchers. Cucumber calls them snippets and SpecFlow implements them as attributes but essentially we’re all doing the same thing. You can either put the matchers in yourself manually or have Lucid help you determine what they are. Try to run Lucid:

$ lucid

Now create a file in your steps directory called triangle_steps.rb and put the following matchers in place:

Note here that for my Then matcher I changed the default argument name that Lucid provides (arg1) to one that is more in line with what I’m capturing (value). This is not necessary but it’s something I like to do.

The Domain Specific Language

These are your test definitions and they are pretty much sitting between your TDL and your DSL. Wait, what DSL? Well, that’s the trick isn’t it? You don’t have one yet. But you do want one. Currently test definitions are written in Ruby and they translate each step in your scenarios into concrete actions in Ruby code.

The DSL comes in because you don’t necessarily want someone to have to write massive amounts of in-depth Ruby code within your test definitions. You want to keep those clean and lean. Or, at least, that’s my contention. Specifically what you want to do is what’s called “pushing the how down” or “pushing how down the stack.” The idea here is that you should not think of test definitions as functions or methods — even though they kind of are. Rather you think of test definitions as the translation layer that connects your Gherkin (TDL) to your automation code (DSL). The only job of the test definition is to make this connection.

So what I feel you want is a DSL that can work alongside — but not be part of — Gherkin. That’s part of what Fluent provides but it’s by no means the only tool or approach that could do so. You’ll see more about this as we go on.

So far the test definitions are exactly like what you’ve done in previous posts. A difference however is that now we are using Fluent and it’s going to allow you to do things a little differently to allow you to adhere to that separation I just mentioned between TDL and DSL. One approach towards this goal is that Lucid and Fluent working together encourage you to adhere to the page object pattern. What that means is that the test definitions are going to delegate all of their work to specific page objects.

By this point you probably know that Lucid itself is written in Ruby. While you do not need to know Ruby in order to execute Lucid or write test specifications, you will need some Ruby knowledge to write some of the test definition matchers. Although, as you’ve seen, Lucid will usually provide these for you. If you plan to use an underlying automation library (whether Fluent or something else, like Capybara) then you will need Ruby skills to write page definitions as well as any test helpers. That being said, the DSL part of this comes in once again by making even that process as easy as possible, which I hope to show as we go through this post.

Page Definitions

In your pages directory create a file called triangle.rb.

Any files in the pages directory are expected to be page definitions. The important thing is that page definition files will generally contain a class (template) that represents a page in the application being tested or, at the very least, some part of a page. I say they will “generally” do that but page definitions can also provide a more abstract notion of something within the application. For example, let’s say you had a navigation bar that was common to every page on your site. You might have a navigation page definition that, technically, does not represent a page at all.

A page definition consists of three specific parts: assertions, element definitions, and action definitions.

Assertions

To get started, put this in your triangle.rb file:

The inclusion of Fluent simply means that this page definition will inherit the DSL that Fluent provides. The use of that will become more evident as we go.

Here url_is and title_is are assertions. They assert that something is true about a page. The reason for doing this is that test definitions can use those assertions. For example, if the URL is asserted for a page, a test definition can use that information to go to that URL or to check if the appropriate URL was returned as the result of a navigation action. Likewise, if the title is asserted for a page, a test definition can use that as one way to check if it is on the correct page.

Keep in mind, the assertions come from Fluent, not Lucid. More specifically, Fluent allows those assertions to be declared on a class if Fluent is included as part of that class, which, as you can see above, it is.

Now let’s add to our test. In your Given test matcher, add this:

This is a call to a page definition that corresponds to the file triangle.rb, which is where our TriangleTest page definition was written. Note here that you are not specifying the file, however, but rather just the name of the definition. That allows you to move your files around or even change their names without dislocating your page definition calls in your test definitions.

The page definitions essentially will tell the test definitions what web elements are on a page and what actions the page knows how to perform. This is very commonly known as the page object pattern. The idea being that when a page definition is set as a context (or instantiated), you end up with a page object.

So what’s happening here is that my test definition is establishing a context for the actions that need to be taken. In this case, the only action to be taken is that the page context must be established for the TriangleTest page. More specifically, the on_view(TriangleTest) call not only establishes the TriangleTest page definition as the context but also indicates that the test should actively go to the URL for that page definition. That’s what the “view” in “on_view” is essentially stating. And the reason it’s even possible to go to the URL is because that URL was asserted via the url_is assertion.

Take a second and make sure you see how that is working.

To reinforce the idea, let’s have a specific check made when the TriangleTest context is established. Change your test action to this:

This will run an automatic check to determine if whatever title is found on the existing browser page matches the title that was asserted in the title_is assertion. If you want to verify that it’s working, simply change the text of the title_is assertion to force an error. The check_title action is defined by Fluent and so you can see here that a test definition can call elements from a test library directly.

Element Definitions

An element here refers to web elements, which are the basic “widgets” you interact with on a web page. Let’s consider how element definitions are declared in page definitions. First, check out the triangle page in your browser. You can see that there are three text fields for entering the side values of a triangle. You can also see that there are three buttons to be clicked. Obviously there’s a lot of text as well and you’ll see that, if you click the buttons, text is generated based on what has been entered in the text fields. So I know I want to interact with those text fields and the buttons. That means I need to define them on the page definition. To do that, add the following to triangle.rb:

Here the element definitions state that there are three buttons and three text fields on this page. There may be more elements on the real page, but these are the only ones that the TriangleTest page definition has been told about and thus, presumably, the only ones that I am concerned about at the moment. What you might notice here is that the element definitions follow this pattern:

element_type  :friendly_name  locator

The element type is generally going to be whatever the actual element is referred to in web design terms: button, table, select_list, checkbox, radio, text_field, etc. The friendly name is the name that you want to refer to the element by in your test definitions or any supporting logic. You’ll see this more in a bit. As an implementation note, the colon (:) prefixing the friendly name makes the name a Ruby symbol. That’s done because it allows me to treat the name dynamically. Essentially the idea of a Ruby symbol is to be able to say “the thing referred to as friendly_name” without having to worry exactly what data type friendly_name is.

Finally, the locator is a way to locate the particular element on the web page. You can use most locators that are commonly used to locate elements via the Document Object Model. Generally you will want to use id but you can also use name, class, index and xpath. In some cases you can use locators that are relevant to a specific object. For example, you can use the text locator for links. In our example, you can see that I used the value locator for the buttons.

You might deduce that a nice thing about this approach is that if the element changes, either in type or in locator, you can simply make that change in one place: the page definition. As long as you keep the friendly name the same, any test definitions that reference this object do not have to change. You might also deduce that you could work with your team to create page definitions before a UI has even been constructed.

Note that, like assertions, the support for element definitions is provided by Fluent. Specifically, Fluent will dynamically create actions (methods) at runtime based on the “friendly name” that you provide. Behind the scenes, the element_type is used as the basis for generator methods in the Fluent framework. The practical upshot is that, with our example, “the thing you call data_conditions” knows how to act like a button and “the thing you call side1” knows how to act like a text field.

Action Definitions

Just as page definitions allow you to define the elements on those pages they represent, you can also define the actions that the page knows how to take or the requests for actions that the page knows how to respond to.

This is crucial because this allows you to keep the specific implementation details out of your test definitions. This means that even if, say, the entire means of how you log in to an application changes, as long as you still provide a “login_as” action (method), your test definitions can continue to call that action. The test steps are insulated from the specific changes to your application. The test definitions known your intent (“I want to log in to the application”) whereas the page definition will tell you the implementation (“here’s how you go about logging in to the application”).

In our first test matcher, until we made the call to check_title, we didn’t actually call an action. We simply established the context for the page to be used. Well, actually, in truth, Fluent did cause an action to be run even in the first case. It’s called the view() action. If you wanted to see that for yourself, you could change the Given matcher temporarily to have this code:

If you try the above out, make sure you change your code back to the on_view approach just so everything is consistent.

To put all this further into context, let’s now consider our When test step. Essentially the idea is to have the person try getting the results of their test when they have done nothing at all. So what I want to do is essentially click the “Evaluate Date Conditions” button, which evaluates all of the data conditions that a person has tried on the page. I’m already navigating to the page via my Given test step, so it looks like my When test just has to click that button.

Since Fluent is using Watir-WebDriver is its browser execution library, I could just have the test matcher look like this:

However, that ties this test matcher to a very specific implementation of what it means to say that “no conditions are tried.” I’d rather keep the intent intact but push the implementation down to the page definition. So, instead, change your When test matcher to this:

Here the on(TriangleTest) call is establishing the TriangleTest page definition as the context but, unlike the use of on_view, it’s not going to go to that page. Instead the call to ‘on’ simply indicates that, by this point in the test, the user should be “on” the TriangleTest page. This is all somewhat of a crucial point: these test definitions don’t say much about what to do. They simply call out to a context and then uses that context to perform some action.

So far our test actions in the test definitions are relying on Fluent and, specifically, they are using the Fluent Factory to create new page objects from the page definitions. The factory methods being used are on_view and on.

What you can also see here is that the test definitions themselves will be relatively free of too much detail. The tests will simply say what to do. The page definitions will know how to do it. This is a way of decoupling implementation details from the tests so that test steps can be relatively insulated from changes. This helps foster a pattern whereby tests are written at the intent level. This is also exactly what I was talking about before regarding a DSL.

With the action we just put in place, we’re saying that the TriangleTest page should know how to do some action we are calling “evaluate_test”. So add the following to your page definition in triangle.rb:

What’s happening here is that the button we gave the friendly name of “data_conditions” is being referenced. In order to let the action definition know that it is dealing with an element definition on the page itself, it’s helpful to preface the call with “self” where ‘self’ refers to the current instance of the page that is being tested. What this call is going to do is actually click the button.

The reason that a click will happen even though it would not seem obvious that it is comes down, again, to Fluent. Fluent provides generated methods at runtime that will take certain actions based on the type of web object it is dealing with.

Incidentally, at any time during this you can be running the test spec by doing this:

$ lucid

I’ll bring up one other thing here: which is the idea of persistent context. You’ll note that in our Given matcher we said “on_view(TriangleTest)” and then in the When matcher we said “on(TriangleTest)” and it might seem odd to you that you have to re-indicate that you are on the TriangleTest page for the When step. Technically, you don’t. When Fluent establishes a page context, it will create an instance of that page that it refers to as @active. That page will be the active page until you have another context set, either via on() or on_view(). So what this means is you could have had your When matcher read like this:

As to whether you should do this or not, it really depends on the granularity of the test step. In the previous way of doing it, the context was clear. In the second way, you are saying it is whatever the @active context is. The latter may work if, say, you have multiple pages where the intent of “no conditions are tried” can be executed. It’s a bit of an involved subject to get into for now, but just know that this is possible. Also note, however, that this very much gets into the area of where your TDL and DSL meet.

Finishing the Test Spec

Now let’s put the final logic in the Then test matcher:

Here you can see that the TriangleTest page must now know how to respond to an action called check_for_score. That action must be given some information, specifically the value of the score, which will be captured as part of the regular expression. So note here that your actions in the page definitions can take in information in order to perform their action. What information they can take in is determined by how you have parameterized the test matcher for a given test step. Let’s add this new action to the page definition in triangle.rb:

Here the “wait_for” action comes from Fluent and essentially says that anything in the enclosing block should take place within ten seconds. That’s how long Fluent will wait before it generates an error condition.

If you run the lucid command right now, you are going to get an error. Try it and see.

You are being told that there is an undefined method called “score”. And, sure enough, if you look at the above code, you might in fact ask: what’s the ‘score’ that is being checked there? That’s meant to be referring to an element on the web page. If you try these actions manually in the application, you will see that a bunch of text is reported and then way at the bottom a score is given. If you were to check the markup, you would see that there is a div called “score” or, rather, that has an id of “score”. So you need to add that you your page definition:

Note that I’m giving it a friendly name of score. That’s what allows me to refer to this element in my logic. Now you can run your test spec and it should pass.

Note that, in reality, you probably would have gone through the page and had the element definitions already established. I took you through it this way just to show how easy it is to add an element definition and incorporate it into your test logic.

As another note, earlier I mentioned that I used “self” with data_conditions and above I used “self” with score. I also indicated that this helped Fluent figure out what you were calling: specifically methods on the TriangleTest object. While this does help Fluent, if there are no competing calls in your source logic that could cause confusion, you can drop the “self” in your action definitions.

TDL and DSL

To close out here, let’s recap a bit about this notion of TDL and DSL. I keep using those terms and you might wonder why a TDL would not itself be considered a DSL. Well, in fact, it is! The TDL is an external, high-level DSL. External DSLs tend to provide a humanizing interface. So I can have this, for example:

Background: Given the physics page

  Scenario: Getting the selected value and option
  When the physics expertise topic "Loop Quantum Gravity" is selected
  Then the selected physics expertise value is "Loop Quantum Gravity"
  And  the selected physics expertise option is "lqg"

That’s a humanizing interface because it does its level best to remove as much programmatic elements from your view as possible.

Fluent is or, rather, provides, an internal, low-level DSL. Internal DSLs, if they are effective, provide what’s called a fluent interface. So let’s consider how the above TDL will be put into test definition form. The Given and the When are pretty simple:

But now let’s consider different ways that Then matchers could be constructed. Consider this:

Each of the actions in the Then matchers are doing the same thing. If you read from top to bottom in each matcher, you’ll see that the code logic gets simpler and simpler. (Well, somewhat anyway.) That’s the idea of a DSL. You start to provide a way to remove as much detail as possible so you can focus on the actions that you want to express.

As you’ve seen by going through this post, even at the highest level of abstraction in those Then matchers, that’s still a bit too low level for me. I prefer to let the page definitions handle the work and have descriptive context and action calls in the test definitions. There’s lots more to say on and I probably risked confusing people with this last bit but I did want to call out why I kept harping on how your test definitions should be calling upon and relying on a DSL, whether that’s provided by Fluent or some other library.

This is a good place to stop this post so you can make sure you take a look at what you did here with Lucid and Fluent working together. Feel free to modify the test spec a bit. You have the triangle application to play around with. In the next post, I’ll build directly off of what we’ve done here.

Share

About Jeff Nyman

Anything I put here is an approximation of the truth. You’re getting a particular view of myself … and it’s the view I’m choosing to present to you. If you’ve never met me before in person, please realize I’m not the same in person as I am in writing. That’s because I can only put part of myself down into words.

If you have met me before in person then I’d ask you to consider that the view you’ve formed that way and the view you come to by reading what I say here may, in fact, both be true. I’d advise that you not automatically discard either viewpoint when they conflict or accept either as truth when they agree.

This entry was posted in Lucid. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *