Monday, November 12, 2012

Cucumber and Jokes

What do Cucumber and jokes have in common? Well, more than you think! Both are communication vehicles.

Wait, you say, when has cucumber, a common vegetable, grown into a communication vehicle? Well, I'm talking about Cucumber with a capitol C, a framework for test automation invented by an Icelander with a hard-to-remember name, Aslak Hellesøy. Aslak's big idea is that behavior of a system can be described in formal language fully comprehensible by mere mortals that are blissfully ignorant of mere existence of programming languages, and yet be convertible into a precise set of instructions used to fully execute tests by computers. Aslak called this language Gherkin (after a particular sub-species of cucumber).

In my experience, in order to use Cucumber effectively, you don't at all need to know how to cook, but it surely helps if you can tell jokes well.

Let me explain through an example:

  1. User enters the restaurant.
  2. User asks waiter for a Sangria.
  3. Waiter brings user a Sangria.
  4. Waiter is carrying a dish with two oval items 4 and 1/4 inches in diameter.
  5. User asks waiter what is in the dish.
  6. Waiter replies the dish is made of testes from a bull killed in a corrida.
  7. User attempts to order an instance of this dish.
  8. Waiter rejects the order explaining that only a single instance of the dish per day
     is available.
  9. Waiter offers to place a similar order to be fulfilled the next day.
  10. User places the order of this dish for the next day.
  11. User finishes his Sangria.
  12. User asks the waiter for a check.
  13. Waiter issues user a bill of 5.75 Euros for the rendered serves.
  14. User gives waiter 6 Euros in cash: 5.75 Euros for his Sangria and leaves 
      0.25 Euros as tip for the waiter.
  15. Waiter takes the cash and greets the user with a smile.
  16. User leaves the restaurant.
  17. 24 hours pass.
  18. User enters the restaurant.
  19. User demands delivery of his order from the waiter.
  20. Waiter delivers the order.
  21. User bites the dish 3 times. The dish tastes very well.
  22. User visually examines the dish. The dish consists of two oval items 
      2 and 3/4 of an inch in diameter.
  23. User waves his hand in waiter's direction. Waiter comes by.
  24. User demands an explanation why dimensions of the fulfilled order do not match 
      dimensions of the previously displayed instance. Waiter explains that sometimes 
      the bull wins the fight.
  25. User stops eating.
  26. User asks for a check.
  27. Waiter issues user a bill of 15.55 Euros for the rendered serves.
  28. User gives waiter 15.55 Euros in cash.
  29. Waiter takes the cash and leaves without a word.
  30. User leaves the restaurant.
Now please answer these questions:
  • Are you still awake?
  • Did you get the joke?
  • Did you find it funny?
  • What's the problem with telling the joke in this form?

    You get 5 points for answering positively to each of the first three questions. You get extra 20 points if you couldn't even provide an answer to the fourth question.

    If you got more than 10 points, your brain seems to have been trained to think of the world in terms of detailed test plans. You may have a few things to learn here…

    If you got less than 10 points, some of this may seem obvious, but hopefully will still provide some useful insights.

    In the 30 steps above, I attempted to tell a joke in the form of a typical manual test plan: most navigational steps are described in full detail, while many of important contextual details are left out, or just implied. Domain-specific terminology is used without explanation, mixed with technical terms. Repeated steps are duplicated without any attempt of reuse. But by far the worst offense is that the key elements of the feature are buried in a sea of irrelevant implementation details.

    OK, you say, but test plans are used by test people and only test people, so they'd better explain all the details of the feature, otherwise, how can anyone verify that the system works as designed?

    Well, that's exactly the problem with test plans as we know them. They are indeed the most detailed specification of the system, but because they are so verbose, and frankly: boring, they are used only by testers and avoided at all cost by all other stakeholders. And because they specify all the details of the implementation, they can be completed only after the implementation has been finalized. Given enough time (which is a rare event) developers may review the test plan once, but no stakeholders other than testers will even dream of maintaining the test plan in its verbose form to keep it up-to-date as the behavior of the system evolves.

    Instead, virtually each stakeholder typically writes and sometimes attempts to maintain their own specifications of their view of the system, which inevitably end up being mutually inconsistent, as well as obsolete. As a consequence, communication between the stakeholders suffers and misunderstandings multiply. How often has it happened that a sales person has sold to a customer a product with one or more features that, once the product actually shipped were nowhere to be found?

    So, how can cucumber and jokes rectify this dysfunctional mess that the industry has brought itself in? Cucumber's premise is that both new and existing features of a system (i.e., system's behavior) can be described in a single specification with just enough details to make the behavior easily understood by all stakeholders, from end users through sales people, to executive sponsors, and thus enable effective communication of all stakeholders as they collaborate to define and iteratively evolve the system's behavior. And the jokes? Well, they are a form of communication that excels in emphasizing the punch-line, just like the system specifications should excel in emphasizing the system's value proposition.

    Let's bring this all together by telling the joke above using Gherkin language and emphasizing the punch-line:

    Scenario: American tourist in Spain
      Given an American tourist in a restaurant in Spain
      And a waiter carrying a peculiar looking dish
      When American asks the waiter about the dish
      Then waiter replies: "Señor, those are the balls of the bull that was 
    killed in this morning's corrida. A real treat!"
      And American says: "I have to try it! Please bring me one portion."
      And waiter replies: "I'm sorry señor, but there's only a single bull fight 
          each morning. But if you want, I can make a reservation in your name
          for tomorrow"
      When the American shows up next day
      And the waiter brings him the dish
      Then American eats a few pieces and takes a closer look at the dish
      And calls the waiter and asks: "This dish is really tasty, but I wonder
          how come the portion today is much smaller than yesterday?"
      And the waiter responds: "Well, señor, sometimes the bull wins..."
    
    Let's see what changed: I've gotten rid of pretty much all the unnecessary details (e.g., the size of the ovals in the dish, the ordering procedure, etc.). I ensured, however, that context important for the punch-line is clear (e.g., that the joke is about American tourist in Spain, someone not acquainted with a local culture). I've kept domain specific terminology (corrida), but ensured the scenario can be understood even by a reader not fully acquainted with it: the story mentions the bull being killed in it, thus the reader can easily figure out it's a spanish word for "bull fight". All this made the it possible to tell the joke in shorter time and thus keep the interest of the listener all the way until the punch line.

    Finally, I've added some keywords: "Scenario", "Given", "When", "Then" and "And". Obviously, these didn't come from the joke-telling tradition, so they must be part of Cucumber's Gherkin language. Indeed, they do make the scenario a bit drier than a well-written, free-form prose, but they also make the scenario executable through a series of transformations. It's worth noting that there are two iterations of when-then steps in this scenario: the first When builds the story to a mini-culmination (unveiling of mysterious dish) indicated by Then steps, while the second series of When steps builds up the rest of the joke to the second culmination in the second series of Then steps. If in doubt when to use 'When' vs. 'Then', remember that the latter should contain the actual punch-line of the joke, in other words, 'Then' steps must show the value provided by the scenario. If you can not think of a logical 'Then' step in your scenario, something is obviously wrong: either your scenario doesn't provide any value, or perhaps it's not quite complete. For example, imagine the effect it would have on the reader if the scenario above stopped just before second series of 'Then' steps!

    In summary, effective communication between stakeholders requires careful balance between discerning important details and abstraction of unimportant ones. Thinking of scenarios as analogous to jokes where punch-line is replaced by business value provided by the scenario helps achieve this balance: everything that can be left out without jeopardizing the punch-line can and should be left out. Or in words of Josh Bloch: "When in doubt, leave it out!"

    I hope you find this analogy useful in your practice. Feel free to share your comments, or perhaps another joke told in Gherkin?

  • Monday, January 30, 2012

    Parallel Software Development Worlds

    This is a story about Joe, a software developer living in parallel worlds.

    In one world, Joe is following the traditional approach to software development: he writes his code, calls it “complete”, and hands it over to Quality Assurance organization for testing. He writes some automated tests after he’s written the code if he has some extra time on his hands, which rarely happens, because he’s constantly under pressure from his management chain to fix defects in the software he developed that were found by the customers. And the little time he has left is spent fighting fires to unblock his QA partners by fixing critical defects in the “complete” code he turned over for testing. This, of course, means that, in order to ship any software to customers, Joe leaves many defects found by QA, but deemed non-critical in his software, in hope that customers won’t run into them. Often, however, they do, and are not exactly thrilled they did, which means that the criticality of such defects is escalated to be much higher than original assessment. Debugger is Joe’s most used tool, and code is full of instructions that dump various messages to a log file that only Joe can (only on his better days) understand. Joe’s customers are irritated by the defects sprayed in his software, and are always on the lookout for alternatives that are coming on the market in hope they’d be of higher quality.

    In a parallel world, Joe is also a software developer, but he’s using a modern, test-driven approach to software development (TDD). This means he writes an automated test before he writes any production code, runs the test to verify that it fails, and only then proceeds to making the test pass by writing the production code that exhibits behavior expected by the test. He then refactors both the test and production code to keep it clean (void of any redundancies) and readable. He then repeats this process until the tests are demonstrating that all the behavior required from the software he’s writing is demonstrable. This way, he finds majority of defects as soon as possible: as soon as they are written, and is able to give his QA partners code that actually works. This allows them to focus on finding integration defects that are typically product of communication breakdowns between Joe and his fellow developers, or between Joe and the actual users of the software he’s writing. Such defects inevitably exist, but are more easily found, since the code is at least behaving the way Joe thought it should. As a consequence, Joe can reproduce such defects more easily, as it typically requires simply modifying some of the automated tests to expect different behavior. Occasionally, it also involves adding more complicated tests that cover corner cases that Joe hasn’t originally thought of. Overall, there are fewer fires to fight, and the software delivered to customers is of much higher quality. Consequently, customers are thrilled to use Joe’s software, and eager to use new versions and features, which Joe has more time to work on, as he rarely gets interrupted by defects escalated by customers.

    Now let’s take a peek into some of the code that Joe is writing. In both of the parallel worlds that we’ll be observing, he’s writing a set of Java classes representing data about products and customers (it looks like Joe has just started writing a new business application). So far, he’s written two classes, Product and Customer:

    public class Product {
     private List<Customer> customers = new LinkedList<Customer>();
    
     public void addCustomer(Customer customer) {
      customers.add(customer);
      customer.addProduct(this);
     }
    
     public Collection getCustomers() {
      return customers;
     }
    
    }
     
    public class Customer {
     private String name;
     private List<Product> products = new LinkedList<Product>();
     
     public Customer(String name) {
      this.name = name;
     }
     
     public String getName() {
      return name;
     }
    
     public void addProduct(Product product) {
      products.add(product);
     }
    
     public Collection<Product> getProducts() {
      return products;
     }
    
    }
    

    So far so good: he’s an experienced Java programmer, and has been able to implement the many-to-many relationship between product and customer class without introducing any defects in the code.

    In the parallel world, where Joe is practicing TDD, he’s also already written the following automated test leveraging version 4 of JUnit framework:

    public class ProductCustomerUnitTest { 
     private static final String CUST_0_NAME = "Francis"; 
     private static final String CUST_1_NAME = "Joanne";
    
     private Product product;
     private Customer[] customers;
    
     @Before
     public void setUp() {
      product = new Product();
      customers = new Customer[2];
      customers[0] = new Customer(CUST_0_NAME);
      customers[1] = new Customer(CUST_1_NAME);
      product.addCustomer(customers[0]);
      product.addCustomer(customers[1]);
     }
    
     @Test
     public void customersForProduct() {
      Collection<Customer> custs = product.getCustomers();
      assertEquals(2, custs.size());
      for (Customer c : custs) {
       Collection<Product> prods = c.getProducts();
       assertEquals(1, prods.size());
       if (c.getName().equals(CUST_0_NAME)) {
        assertEquals(customers[0], c);
       } else {
        assertEquals(customers[1], c);
       }
      }
     } 
    }
    
    Incidentally, this test is testing every single line of production code listed above, which gives Joe in this (TDD) world additional piece of mind, knowing (rather than guessing) that his code actually works as he expects it to. Note he’s already refactored test set up into a separate method, in order to facilitate addition of forthcoming additional tests.

    Now Joe is working on data serialization and deserialization using Jackson JSON processor, so he decides to add a new method that allows setting all of the customers for a product at once. In order to leverage the existing code, he decides to implement new method of the Product class  as following:
     public void setCustomers(Collection<Customer> customers) {
      for (Customer c : customers) {
       addCustomer(c);
      }  
     }
    
    In yet another parallel universe, Joe would simply check this code into the source repository and move on. However, in this universe, Joe is being extra careful. Reviewing this code, Joe realizes that the behavior of the method is not quite fulfilling the expectations its name is implying: it’s only adding new customers, but any pre-existing customers stay there. Following the principle of minimal astonishment, he decides that this method should remove any customers previously added to this product and changes the implementation to:
     /**
      * Changes the collection of customers to the provided one.
      * Any previously registered customers are removed.
      * @param customers new collection of customers for this product
      */
     public void setCustomers(Collection<Customer> customers) {
      customers.clear();
      for (Customer c : customers) {
       addCustomer(c);
      }  
     }
    
    At this point, Joe is satisfied with the behavior that he took the time to clarify in the associated API documentation and moves on to implement next feature.  Deserialization from JSON works great and the product is delivered to the customers. However, some time thereafter, there is a huge defect that a customer has discovered and escalated to Joe’s CEO: in a particular scenario of product sales, Joe’s software has been causing their production system to fail to deliver ordered products, resulting in financial loss due to penalties and customer dissatisfaction. After seven weeks of painful tracing and debugging of the whole production system, the root cause of the issue is finally isolated and traced right to the code above: one of the Joe’s colleague’s has used this method to implement an alternative scenario that both he and QA failed to test! Joe has lost countless nights debugging, and his reputation of a solid, experienced programmer is forever lost, at least in the eyes of his management chain, and Joe’s company has lost yet another head of QA department, as well as credibility with one of his most important customers and in the marketplace in general.

    Meanwhile, in the parallel world, Joe practicing TDD had added the following test before changing any code in Product class:
     @Test
     public void setCustomers() {
      Collection<Customer> custs = new LinkedList<Customer>();
      custs.add(customers[1]);
      product.setCustomers(custs);
      Collection<Customer> result = product.getCustomers(); 
      assertEquals(custs, result);
     }
    
    Joe then added empty stub for setCustomers() method to Product class in order to allow the test to compile and fail. Only then he proceeded to implement the method in the exactly the same way as Joe in the parallel world that has not been practicing TDD. However, in this parallel universe, the test above ruthlessly indicated there was an issue with the implementation: the assertion at the end of the test was failing. This made Joe take a second look at the production code he’s just written and notice that he’s been resetting the collection that was being passed as the method argument, instead, as intended, the collection that has been stored as member variable of the class, because they happened to share the same name. The fix is as simple as qualifying collection to be reset with the keyword ‘this’:
     /**
      * Changes the collection of customers to the provided one.
      * Any previously registered customers are removed.
      * @param customers new collection of customers for this product
      */
     public void setCustomers(Collection<Customer> customers) {
      this.customers.clear();
      for (Customer c : customers) {
       addCustomer(c);
      }  
     }
    
    Now the customersForProduct test happily passes and Joe knows he’s done with implementation of this method and can move on with his work. The defect that ruined Joe’s career in the parallel universe has been found and fixed within seconds of its creation, when its impact was no worse than a minor surprise to Joe when a test that was supposed to pass failed to do so. Fixing this defect took Joe less than a minute because he knew exactly what code was causing the test to fail, and he had written it only seconds ago, so he knew exactly what it was supposed to do.

    In contrast, in the parallel universe where Joe was not practicing TDD, by the time the escalation came back to him, he had completely forgotten everything about this piece of code, so it took him much longer to reconstruct his mental state in order to come up with the simplest solution. But the worst of all was that by then vast majority of the damage had already been done – the defect had been silently ruining Joe’s career, and finding it was akin to finding it a needle in a haystack, when the scope of the search was the full, complex system.

    So, next time you set out to write some production code, remember you actually have the power to choose the parallel universe you want to live in! Which one will you choose? I welcome your thoughts and related experiences in the comments section!