Saturday 21 February 2015

C++ BDD with Igloo/Bandit/Homebrew


It's been a while since I posted, so I thought it would be good to give an update.

We had some problems working with SpecFlow using C++, mainly around the "shim" layer we created to provide interopability.

The major issues were :-

1. Debugging ("the breakpoint game" trying to debug across layers)
2. C++ developers are not C# developers, and not all of them want to be
3. Management perception of reliance on C# as a risk
4. Fragile shim layer that doesn't allow the transport of all data types/classes

There were also a plethora of "niggles" which we mostly worked out, but overall no one was particularly happy with the solution.

Instead we decided to try "Igloo", a C++ BDD framework one of our developers found (we layer upgraded this to "Bandit"). It essentially provides macros to facilitate human readable specification tests :-
http://banditcpp.org/

We augmented this and developed Python scripts that allow us to translate a feature file something like this :-

 

   Feature: EatingApple

   Scenario: First bite


    Given an apple waiting to be eaten
    When teeth are sunk into it
    Then juice flies around it



Into this :-
Feature("EatingApple") Handler(EatingAppleSteps)
 
   GTestID(first_bite, EatingApple)
   Scenario("First bite")
      Given("an apple waiting to be eaten") StepPlay(GivenAnAppleWaitingToBeEaten)
      When("teeth are sunk into it") StepPlay(WhenTeethAreSunkIntoIt)
      Then("juice flies around it") StepPlay(ThenJuiceFliesAroundIt)
   EndScenario

At this stage we are now looking at directly executable C++ code. Cool, right? It uses something rather magical called a StepHandler class. As you can probably see, it manages the parsing of parameters in the human-readable feature file (I've left them out of the above example for simplicity) :-
class StepHandler
{
public:
   std::string GetCurrentLine();
public:
   void SetCurrentLine(std::string line);
   void Echo();
   void NotImplemented();
   int get_CurrentRow();
   void set_CurrentRow(int currentRow);
   void IncrementRow();
protected:
   std::vector<std::string> m_parameters;
   std::string m_currentLine;
   int m_notImplemented = 0;
   void ClearParameters();
   void ExtractParameters();
   void StripDelimiters(std::string& parameter);
   std::string GetStringParameter(int index);
};

The outputted C++ code's Handler() macro indicates which step handler the StepPlay() macros use to make their calls :-
class EatingAppleSteps : public StepHandler
{
public:
   EatingAppleSteps();
   ~EatingAppleSteps();
   void GivenAnAppleWaitingToBeEaten();
   void WhenTeethAreSunkIntoIt();
   void ThenJuiceFliesAroundIt();
};

In case you hadn't already realised, the constructor and destructor constitute the setup and tear down for each of the tests.

Beautiful, right? We now have a full C++ BDD system with many supporting scripts, fully integrated with google test, and are very happy programmers again.

Credit to James for his amazing work on this system and Seb Rose for the warnings which helped us avoid several dangerous pitfalls on the way - primarily the importance of synchronised feature/executable feature files. We generate an error in the build if they don't match. This was an excellent steer.

Feel free to contact me if you have any questions :)