Starting Out With Lucid, Part 2

In the first post in this series I took you through some steps to use Lucid by creating the start of a test specification and seeing how that specification could be made executable. This post will continue the process, building on what was already created, but also getting into some details about the conventions Lucid uses for directories where it looks for files.

From the last post, you should have a specs/triangle.spec file that looks like this:

Ability: Triangle Calculation

  As a math teacher
  I need a triangle calculator
  so that I can have students in my classroom learn about triangles
  
  The possible triangle types are:
  
  - Scalene:     all side lengths are different
  - Isosceles:   two side lengths are equal
  - Equilateral: all side lengths are equal
  
  Invalid side lengths will not form a valid triangle.
  
  A "right" triangle has one angle which is 90 degrees.
  This means the side lengths form a Pythagorean triple and
  thus satisfy the Pythagorean theorem.

  Scenario: Scalene
    Given the length values 10, 8, 3

From the last post, you also should have a specs/triangle_steps.spec file that looks like this:

With that in place, now let’s put some logic in the Given test step matcher:

I’m not doing much here: I’m just establishing a context by setting up some instance variables to hold the captured details from the test step. Those instance variables will be usable in other steps. If you run the lucid command, this will pass. That said, it’s not doing much, so this step passing is not terribly surprising.

Now, one thing to note here is that this will pass no matter what numbers you use. But another thing to note is that this will not pass no matter what you do in your TDL. For example, you could have the following TDL statements:

Given the length values a, b, c
Given the length values 10, test, 3

Those will no longer be matched because the matcher is expecting three numbers.

Let’s add two more steps to flesh out our scenario. Update your scenario in triangle.spec so it looks like this:

  Scenario: Scalene
    Given the length values 10, 8, 3
    When  the triangle is calculated
    Then  the triangle type is "scalene"

If you run lucid now, you’ll be given matcher suggestions for those new steps you just added. Add those to your triangle_steps.rb file so it looks like this:

If you run the lucid command with this in place, you’ll see that the first step passes (we know that already), the second step is marked as “pending” and the third step is marked as “skipped”. The third step is skipped because until the pending step before it is made executable, Lucid knows there is no point running the next step in the chain.

An “App” For Lucid to Run Against

Okay, so now we have to do something. Specifically, we have to calculate a triangle. Which brings up a good question: what exactly is Lucid going to be testing against here? Any library or application that could be called via logic in your test definitions would be capable of being tested. Keep in mind that Lucid is not the tool doing the testing; Lucid will delegate to libraries or third-party tools for that. To make things simple initially I will have the code for our “triangle application” simply be some Ruby logic.

So in your specs directory create a file called triangle_calc.rb. Put the following in it:

This is just to get some logic in place so we can simulate hitting a real application. There’s no need for you to necessarily understand exactly how the code works since it is purposely made (perhaps overly) concise to keep this simple.

With that in place, we can now fill in our When step. Add the following logic to it:

Here I’m just calling the method we created in triangle_calc.rb, passing it the side values I got from the Given step. With that addition in place, try to run lucid again.

You’ll be given a comparison error. What’s basically happening here is that the capture groups from the matchers pulled in the arguments as strings. So even though Lucid was told to look for numeric data in the Given step, it still brought that data in as a string. Now in the When step those string values — stored in the @side instance variables — are being passed to the triangle() method which is acting as if they are numeric data.

One way to handle this would be to simply convert the captured step arguments to numeric values when they are handled in the Given step. So you could, for example, do this:

Notice the “.to_i” part, essentially converting each arg parameter into an integer. That would certainly work. The issue here, however, may be one we want to solve more generally. What I’d really like to do is tell Lucid that if you are expecting numbers — since you specifically called out matching via (\d+) — then every time you do such a match, automatically transform whatever you have captured into a numeric value.

Add the following to your triangle_steps.rb file:

What this does is put a Transform directive in the logic that Lucid is reading. Any transform directive will convert (transform) a certain captured string into a different format or structure. Here the idea is that any string captured via \d+ will be converted to a numeric format via the same .to_i method I showed before. In this case, the benefit is small but if you had many matchers that were relying on any values captured via \d+ to be numeric, you can probably see how this would not only save typing time but also remove the need to remember to add .to_i to all such arguments.

If you were to run the lucid command, you would find the step passes.

We’ve got one more step to add logic for, so let’s do that:

If you run lucid with that in place, you should see all steps are passing. You could test a failing condition either by changing some logic in triangle_calc.rb or, in a better test, change the Then step to be inaccurate and see what happens.

Admittedly, this is a contrived example, but with it you can see a full pass-through of creating a passing scenario and allowing that scenario to fail.

Considering Your Directories

Now let’s consider a little bit about structure. What you have now is a single directory, with all files placed in one location. Further, it happens to be the standard location for the repository: specs. You have this:

.
+---specs
        triangle.spec
        triangle_calc.rb
        triangle_steps.rb

You could take another approach, which is to have a separate directory for your source code — such as the test definitions — and then keep your test specifications on their own. There are many ways to do that. Perhaps one of the easier ways is to simply have a directory within your specs directory. For example, if you could restructure your directories this way:

.
+---specs
    ¦   triangle.spec
    ¦
    +---library
            triangle_calc.rb
            triangle_steps.rb

If you did this and run the lucid command you would find that everything works as before. The reason for this — and it might be good to run Lucid with the verbose option to check out what’s happening — is that Lucid will read everything in the specs repository. It will then work to sort out what it is reading. Essentially, though, it’s pretty simple: anything that is using a spec file extension will be treated as a test specification, while anything that has an .rb extension will be treated as code logic.

You could also have the test specifications reside in their own directory and have the library directory be on its own, outside of the spec repository. So you could structure your directories like this:

.
+---library
¦       triangle_calc.rb
¦       triangle_steps.rb
¦
+---specs
        triangle.spec

However if you try to run the lucid command now, you’ll have a problem in that Lucid is back to telling you that you need some matchers. The reason Lucid is not finding your existing matchers is because you are now using a directory structure that Lucid does not, by default, support. After all, the tool can’t possibly know every place you might want to put files. But what you can do is tell Lucid where it should look for certain things.

The easiest way to do this is to simply require that Lucid look in the directory. You could do the following:

$ lucid -r library

There is also the concept of a library path that Lucid treats as slightly differently than just any other directory. Essentially the library is meant to indicate code that Lucid will want and need to reference periodically as part of its operations, sort of like going to a library to look up what you need. If the directory in question is meant to be your library path, you could do this instead:

$ lucid --library-path library

The distinction may not be as meaningful right now but this distinction will come up again in a later post. Now change your library directory to the name common so that your structure looks like this:

.
+---common
¦       triangle_calc.rb
¦       triangle_steps.rb
¦
+---specs
        triangle.spec

Now if you just run the lucid command, everything will work. Why is that? Why did you have to require the library directory but not require the common directory? It’s the same reason why you don’t have to specify the specs directory: Lucid looks for it automatically. The same applies to a directory called common. Lucid will assume that a directory called common, at the same level as your specs directory, will contain common code that you want Lucid to be able to reference.

Now let’s do another slight modification. Create a steps directory, at the same level as your common and specs directory. Now move triangle_steps.rb into that steps directory. Your structure will now look like this:

.
+---common
¦       triangle_calc.rb
¦
+---specs
¦       triangle.spec
¦
+---steps
        triangle_steps.rb

If you run lucid with this structure, you will find everything works. Why is that? Because, once again, steps is a directory that Lucid will use if it finds it.

In fact, right here you are seeing one of the conventions of Lucid, which is that it will use certain directory structures if they are present. If you want to use your own directories, you simply have to require the directory when you run Lucid. So, for example, if instead of common, steps, and specs, you had a directory structure like this:

.
+---support
¦       triangle_calc.rb
¦
+---features
¦       triangle.spec
¦
+---tests
        triangle_steps.rb

Then you could run Lucid like this:

$ lucid -r support -r tests features

You can also use ‘–require’ in place of ‘-r’ if you feel it aids clarity. If you remember from the first post, you can also use a lucid.yml file to specify your configuration. So if you just wanted to run Lucid at the command line without all the options, you could create a lucid.yml file with the following in place:

This is probably a good place to close out this post. In the next post I’ll show a few more details of using Lucid, which will probably take us to the point where our minimal triangle app will have served its purpose and needs to be abandoned. Following that will then be a web-based version of the triangle app so that we can explore how Lucid generates projects for testing against web applications, along with the concept of driver files.

Share

This article was written by 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.

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.