Using Should DSL to write succinct assertions with Lettuce

Lettuce (http://lettuce.it) is a BDD tool built for Python.

The Should DSL (http://www.should-dsl.info) provides a simpler way to write assertions for Thens.

This recipe shows how to install Lettuce and Should DSL. Then, we will write a test story. Finally, we will wire it into our shopping cart application using the Should DSL to exercise our code.

Getting ready

For this recipe, we will be using the shopping cart application shown at the beginning of this chapter. We also need to install Lettuce and its dependencies:

  • Install lettuce by typing pip install lettuce
  • Install Should DSL by typing pip install should_dsl

How to do it...

With the following steps, we will use the Should DSL to write more succinct assertions in our test stories:

  1. Create a new directory called recipe33 to contain all the files for this recipe.
  2. Create a new file in recipe33 called recipe33.feature to contain our test scenarios.
  3. Create a story in recipe33.feature with several scenarios to exercise our shopping cart.
    Feature: Shopping cart
      As a shopper
      I want to load up items in my cart
      So that I can check out and pay for them
    
        Scenario: Empty cart
          Given an empty cart
          Then looking up the fifth item causes an error
          And looking up a negative price causes an error
          And the price with no taxes is 0.0
          And the price with taxes is 0.0
    
        Scenario: Cart getting loaded with multiple of the same
          Given an empty cart
          When I add a carton of milk for 2.50
          And I add another carton of milk for 2.50
          Then the first item is a carton of milk
          And the price is 5.00
          And the cart has 2 items
          And the total cost with 10% taxes is 5.50
    
        Scenario: Cart getting loaded with different items
          Given an empty cart
          When I add a carton of milk
          And I add a frozen pizza
          Then the first item is a carton of milk
          And the second item is a frozen pizza
          And the first price is 2.50
          And the second price is 3.00
          And the total cost with no taxes is 5.50
          And the total cost with 10% taxes is 6.05
  4. Write a set of assertions using Should DSL.
    from lettuce import *
    from should_dsl import should, should_not
    from cart import *
    
    @step("an empty cart")
    def an_empty_cart(step):
        world.cart = ShoppingCart()
    
    @step("looking up the fifth item causes an error")
    def looking_up_fifth_item(step):
        (world.cart.item, 5) |should| throw(IndexError)
    @step("looking up a negative price causes an error")
    def looking_up_negative_price(step):
        (world.cart.price, -2) |should| throw(IndexError)
    
    @step("the price with no taxes is (.*)")
    def price_with_no_taxes(step, total):
        world.cart.total(0.0) |should| equal_to(float(total))
    
    @step("the price with taxes is (.*)")
    def price_with_taxes(step, total):
        world.cart.total(10.0) |should| equal_to(float(total))
    
    @step("I add a carton of milk for 2.50")
    def add_a_carton_of_milk(step):
        world.cart.add("carton of milk", 2.50)
    
    @step("I add another carton of milk for 2.50")
    def add_another_carton_of_milk(step):
        world.cart.add("carton of milk", 2.50)
    
    @step("the first item is a carton of milk")
    def check_first_item(step):
        world.cart.item(1) |should| equal_to("carton of milk")
    
    @step("the price is 5.00")
    def check_first_price(step):
        world.cart.price(1) |should| equal_to(5.0)
    
    @step("the cart has 2 items")
    def check_size_of_cart(step):
        len(world.cart) |should| equal_to(2)
    
    @step("the total cost with 10% taxes is 5.50")
    def check_total_cost(step):
        world.cart.total(10.0) |should| equal_to(5.5)
    
    @step("I add a carton of milk")
    def add_a_carton_of_milk(step):
        world.cart.add("carton of milk", 2.50)
    
    @step("I add a frozen pizza")
    def add_a_frozen_pizza(step):
        world.cart.add("frozen pizza", 3.00)
    
    @step("the second item is a frozen pizza")
    def check_the_second_item(step):
        world.cart.item(2) |should| equal_to("frozen pizza")
    
    @step("the first price is 2.50")
    def check_the_first_price(step):
        world.cart.price(1) |should| equal_to(2.5)
    
    @step("the second price is 3.00")
    def check_the_second_price(step):
        world.cart.price(2) |should| equal_to(3.0)
    
    @step("the total cost with no taxes is 5.50")
    def check_total_cost_with_no_taxes(step):
        world.cart.total(0.0) |should| equal_to(5.5)
    
    @step("the total cost with 10% taxes is (.*)")
    def check_total_cost_with_taxes(step, total):
        world.cart.total(10.0) |should| close_to(float(total), 
                                                delta=0.1)
  5. Run the story.
    How to do it...

How it works...

The previous recipe (Writing a testable story with Lettuce), shows more details on how Lettuce works. This recipe demonstrates how to use the Should DSL to make useful assertions.

Why do we need Should DSL? The simplest checks we write involve testing values to confirm the behavior of the shopping cart application. In the previous recipe, we mostly used Python assertions.

assert len(context.cart) == 2

This is pretty easy to understand. Should DSL offers a simple alternative.

len(context.cart) |should| equal_to(2)

Does this look like much of a difference? Some say yes, others say no. It is wordier, and for some this is easier to read. For others, it isn't.

So why are we visiting this? Because, Should DSL has more than just equal_to. There are many more:

  • be: check identity
  • contain, include, be_into: verify if an object is contained or contains another
  • be_kind_of: check types
  • be_like: checks using a regular expression
  • be_thrown_by, throws: check that an exception is thrown
  • close_to: check if value is close, given a delta
  • end_with: check if a string ends with a given suffix
  • equal_to: check value equality
  • respond_to: check if an object has a given attribute or method
  • start_with: check if a string starts with a given prefix

There are other alternatives as well, but this provides a diverse set of comparisons. If we imagine the code needed to write assertions that check the same things, then things get more complex.

For example, let's think about confirming expected exceptions. In the previous recipe, we needed to confirm that an IndexError is thrown when accessing an item outside the boundaries of our cart. A simple Python assert didn't work, so instead we coded this pattern.

try:
    world.cart.price(-2)
    raise AssertionError("Expected an IndexError")
except IndexError, e:
    pass

This is clunky and ugly. Now, imagine a more complex, more realistic system, and the idea of having to use this pattern for lots of test situations where we want to verify that proper exception is thrown. This can quickly become an expensive coding task.

Thankfully, Should DSL turns this pattern of exception assertion into a one-liner.

(world.cart.price, -2) |should| throw(IndexError)

This is clear and concise. We can instantly understand that invoking this method with these arguments should throw a certain exception. If no exception is raised, or a different one is raised, it will fail and give us clear feedback.

Tip

If you notice, Should DSL requires the method call to be split up into a tuple, with the first element of the tuple being the method handle, and the rest being the arguments for the method.

There's more...

In the sample code listed in this chapter, we used |should|. But Should DSL also comes with |should_not|. Sometimes, the condition we want to express is best captured with a |should_not|. Combined with all the matchers listed earlier, we have a plethora of opportunities to test things, positive or negative.

But don't forget, we can still use Python's plain old assert if it is easier to read. The idea is to have plenty of ways to express the same verification of behavior.

See also

Writing a testable story with Lettuce

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.219.130