BDD with FlexUnit using separate files

We will now try to remedy some of the problems of the methodology explained in the previous post.

Source code for this approach is available here.

Contexts, Because and Specifications

Lets first look at how our tests (or specifications as they are called in the behavior driven environment) are organized.

Contexts

  • Main Context
    • usually we will create a context class that sets up the most general requirements in order to test a class.
    • at the least it will instantiate it and more often than not also inject its dependencies.
    • I usually name my main context classes after the class I’m testing with a “Specs” suffix e.g. “EmployeesSpecs” in order to communicate that it sets up at least part of the context used by all specifications for the Employee class
  • Context
    • any time that a sub context needs to be setup I will create a separate class for it and specifications that use it
    • this context inherits from the main context
    • additionally it sets up requirements needed to test the behavior that the specifications in the containing class need
    • there is a maximum of one context per class

Because

  • contains the code that acts on the system under test after the context was fully specified
  • is optional and will not be present if no action is needed e.g. when some initial criteria need to be checked
  • there is a maximum of one because method per class

Specifications

  • check the result(s) of the action inside the because method
  • there can be numerous specifications in one class
  • specifications should not act on or write to the system under test or its dependencies, but only read out the results
  • any actions needed should not be part of the specifications, but are performed inside the because method, therefore the specification methods will most likely be one liners

The SpecificationBase class

In order to help in BDD , I created a Specification base class which all my Main Contexts (Specs classes) will inherit from.

Essentially all it does is hide away the FlexUnit4 mechanics of preparing the tests and allows to set them up in a more BDD agnostic way.

It also includes a method which allows to write assertions in BDD lingo.

Don’t worry about the catchError() method for now.

Again, reading the comments will help in understanding how it works.

 1 
 2 package specutils.base
 3 {
 4     import specutils.extensions.SpecificationExtensions;
 5 
 6     /**
 7      * Acts as a base class for all specifications to help with BDD.
 8      *
 9      * @author tlorenz
10      */
11     public class SpecificationsBase
12     {
13 
14         protected var caughtError : Error;
15 
16         [Before]
17         public function setup() : void
18         {
19             context();
20             because();
21         }
22 
23         /**
24          *
25          * @param actual the object whose value we are testing
26          * @return the actual object wrapped in a SpecificationExtensions class that adds methods like shouldEqual, shouldBeTrue, etc.
27          *
28          */
29         protected function the(actual : Object) : SpecificationExtensions
30         {
31             return new SpecificationExtensions(actual);
32         }
33 
34         /**
35          * Sets up the context for the specs in the current class.
36          * IMPORTANT: Always call super.context(); when overriding this!
37          *
38          * This can be used in the main context to setup things that apply to all specs.
39          * Ideally at that point the dependencies should be prepared and the sut constructed.
40          *
41          * Any more specific setups should be done inside the current context class.
42          * Here we should setup things that are specific to the behavior we are testing.
43          *
44          */
45         protected function context() : void
46         {
47         }
48 
49         /**
50          * After the context has been setup and the sut is thus prepared, we act on it.
51          * e.g. we invoke a method, set a property or cause a dependency (mock) to interact with it.
52          * The results of this action (normally it should be only one) will then be checked inside the test methods.
53          *
54          */
55         protected function because() : void
56         {
57         }
58 
59         /**
60          *
61          * This is a helper that allows to execute a piece of code that will most likely cause an error.
62          * After invoking it, we can examine the caughtError property to validate that the expected type
63          * of error was thrown and that the message contained what we expected.
64          *
65          * This approach is more flexible than putting a meta-tag for the expected error on the test method.
66          *
67          * @param func
68          * the function that will cause the error
69          *
70          */
71         protected function catchError(func : Function) : void
72         {
73             try
74             {
75                 func();
76                 caughtError = null;
77             }
78             catch (e : Error)
79             {
80                 caughtError = e;
81             }
82         }
83     }
84 }

As you can see the [Before] method, which is invoked before each test (spec), invokes the context() followed by the because(), consequently setting up the system under test and its dependencies and then acting on it.
We will override these methods in order to specify the contexts and actions.

The “the” method is there to “extend” our objects with “should” methods. It returns a SpecificationExtensions object and injects the value we are trying to check.

Here is the constructor and one of the methods inside the SpecificationExtensions class:

 1 public function SpecificationExtensions(actual : Object) : void
 2 {
 3     _actual = actual;
 4 }
 5 
 6 private var _actual : Object;
 7 
 8 public function shouldEqual(expected : Object) : SpecificationExtensions
 9 {
10     assertThat(_actual, equalTo(expected));
11     return this;
12 }

As we can see, calling the(value).shouldEqual(1); is only syntactic sugar for assertThat(value, equalTo(1));.
It reads much nicer though and speeds up coding due to the intellisense that pops up and allows us to choose the appropriate should method.

It is also possible to add these methods to the Object.prototype and that way allowing value.shouldEqual(1); , but of course there will no intellisense support in that case.

Since I am such a code assist junkie that makes it a no go for me.

Here is a little taste of the convenience:

In case you are wondering why it returns itself, it is to allowto code multiple assertions in a fluent style (this is very rare as ideally there should be only one assertion per specification).

Just for kicks, it would allow me to do this:

1 the(_sut.salary)
2     .shouldNotEqual(1)
3     .shouldNotEqual(2);

The Employee tests revisited

For now we have to part with the convenience of having all our tests for one class inside one file.

This is due to the limitation/rule of the Flex compiler that only allows one public class per ActionScript file and the fact that FlexUnit will not be able to find any tests contained in private classes.

In the next part I will show how to work around this.

For now we want to stick to the rules though in order to enjoy all the benefits of the integrated FlexUnit4, including the automated creation of a test runner.

As a starts we have the main context inside the EmployeeSpecs that does what the [Before] setup() method did in the previous example:

 1 package bddseparate.specs.accounting.company.employee
 2 {
 3     import accounting.company.Employee;
 4     import bddseparate.fakes.BankAccountMock;
 5     import specutils.base.SpecificationsBase;
 6 
 7     public class EmployeeSpecs extends SpecificationsBase
 8     {
 9         protected var _bankAccount_Mock : BankAccountMock;
10         protected var _sut : Employee;
11 
12         override protected function context() : void
13         {
14             _bankAccount_Mock = new BankAccountMock();
15             _sut = new Employee(_bankAccount_Mock);
16         }
17     }
18 }

Then we have the following two contexts along with more specific sub contexts:

  • the bank account is connected
    • the salary is 1 dollar
    • the salary is 2 dollars
  • the bank account is not connected

Since we can only have one context per class, we need to organize our packages/classes in the following way.

EmployeeBDDSeparateOutline

It reads nicely from top to bottom e.g. employee.when_bank_account_is_connected.and_salary_is_1_dollar_paying_him.should_update_the_bankaccount

The test results are also very informative. If any test fails, looking at them will give us very detailed information.

bddSeparateResult

Despite all these details the individual test names are not as verbose as before (e.g. “should_update_the_bank_account”) since a lot of information is contained in the package and class names.

The major problem with this approach is obvious though – packages and files exploded right in our face.

Where there was one file containing all employee tests, we now have 7 and need 2 extra packages to get them organized.

Walking the specification path

For completeness sake I’ll explain how it works – if it is too confusing, skip to the next approach which is easier to look at and will make a lot of things clear.

As I said, the main context is set up inside the EmployeeSpecs and all classes containing specs either directly (e.g. initially.as) or indirectly inherit from it.

Lets take our previous example (employee.when_bank_account_is_connected.and_salary_is_1_dollar_paying_him.should_update_the_bankaccount) and follow it all the way through.

Here is the class containing the actual specifications:

 1 package bddseparate.specs.accounting.company.employee.when_bank_account_is_connected
 2 {
 3 
 4     public class and_salary_is_1_dollar_paying_him extends Ctx_BankAccount_is_connected
 5     {
 6         private const SALARY : Number = 1;
 7 
 8         override protected function context() : void
 9         {
10             // make sure we call the context setup of Ctx_BankAccount_is_connected first
11             super.context();
12             _sut.salary = SALARY;
13         }
14 
15         override protected function because() : void
16         {
17             _sut.pay();
18         }
19 
20         [Test]
21         public function should_set_was_paid_to_true() : void
22         {
23             the(_sut.wasPaid).shouldBeTrue;
24         }
25 
26         [Test]
27         public function should_update_the_bank_account() : void
28         {
29             the(_bankAccount_Mock.updateWithAmountWasCalled).shouldBeTrue;
30         }
31 
32         [Test]
33         public function should_update_the_bankaccount_with_1_dollar() : void
34         {
35             the(_bankAccount_Mock.updateWithAmountWasCalledWith).shouldEqual(SALARY);
36         }
37     }
38 }
39 

As we can see, after setting up the specific context and acting on the system under test via the because method, it makes sure that the three specifications are met.

Note, that it inherits from the Ctx_BankAccount_is_connected class and calls its context() method before further defining its own context.

Here is the Ctx_BankAccount_is_connected class:

 1 package bddseparate.specs.accounting.company.employee.when_bank_account_is_connected
 2 {
 3     import bddseparate.specs.accounting.company.employee.EmployeeSpecs;
 4 
 5     public class Ctx_BankAccount_is_connected extends EmployeeSpecs
 6     {
 7         override protected function context() : void
 8         {
 9             // Make sure we build up onto the main context
10             super.context();
11             _bankAccount_Mock.isConnected = true;
12         }
13     }
14 }
15 

As you can see it extends from the EmployeeSpecs class and now it should be clear how the context is created from the most general one inside the EmploySpecs and gets more and more specific as we walk up the inheritance chain until we arrive at our actual specifications.

Summary

Advantages

  • ability to convey lots of information without verbose test names
  • contexts are built up incrementally and are centralized which makes for easier maintenance
  • specific/general contexts clearly communicate what is needed for individual/all specifications
  • actions and assertions are clearly separated into because() and [Test] methods
  • specifications are easy to read as they are one liners
  • also integrates fully with the integrated FlexUnit framework and thus there is no need to create custom test runners

Disadvantages

  • file/package explosion
  • harder to understand and follow for people not familiar with the concept

In the next post I will explain how to avoid the packages/files explosion and will enter mockito into the picture.

Advertisements
  1. Leave a comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: