BDD with FlexUnit the traditional TDD way

Implementing tests the traditional TDD way

Source code for this approach is available at here.

We are now ready to look at the tests that we need for the project created in the previous post.

First of all we have to decide what we will be testing.

In this case I wanted to make ensure two things:

  1. that the Employee keeps track of its own status correctly (was he paid, fired?)
  2. that the Employee interacts correctly with the bank account
  3. that the Employer interacts correctly with the Employee

Note that at this point I am not worried about the functionality of the bank account as this will be implemented later.

The test project

I don’t believe in including test code inside my production code projects, so lets create a separate Flex library project for our tests.

In it we include a fakes package and a tests package.

The fakes package will contain the necessary mocks and stubs.

Inside the tests package we mirror the exact package structure of the project we are testing in order to easily find the classes and their related tests.

For example the Employee.as is inside the accounting.company package and so we put the EmployeeTests.as inside our test project inside the the tests.accounting.company package.

A short excursion into fakes

For a more detailed introduction please read the mock Wikipedia page.

I will just mention some core points.

  • Mocks vs. Stubs
    • mocks help decide if a test will pass by having things verified, while stubs just stub out properties or method return values
    • as a general rule if a fake is not used in  a verification it is a stub, otherwise it is a mock
    • adhering to strict naming conventions makes it easier to see how a fake object is used in the tests
  • fakes and top down development
    • fakes help in developing our application test driven and top down
    • in our example, I can test the interactions of the Employee with the bank account without having implemented the latter. All I need is its interface and a mock that implements it. This way I can flesh out, what it needs to be able to do, before starting with the actual implementation. That way I avoid implementing behaviors that will not be needed after all. The design will also be improved because I see how it a class will be used while I am creating it.
  • fakes, dependency injection and interfaces
    • it is good practice to create an interface for every dependency and inject it as an interface (instead of as a concrete class) into the dependent class either via the constructor or its properties
    • while this is just good design, it makes testing much easier as it allows replacing the dependencies with fakes while testing the dependent class

As I said, his is a very brief look at the advantages of using fakes. For further reading consult the books in the suggested readings or the above mentioned wiki page.

The BankAccount Mock

Here is the code of the BankAccount interface inside the Accounting project:


package accounting.interfaces
{

	public interface IBankAccount
	{
		function updateWithAmount(amount : Number) : void;

		function get isConnected() : Boolean;
	}
}

The mock implements the interface, but has no real functionality:


package traditional.fakes
{
	import accounting.interfaces.IBankAccount;

	/**
	 * This class is just keeping track of what is done to it.
	 * It will be injected instead of the real thing and afterwards I can check if the System under Test
	 * interacted with it in the desired way.
	 * e.g. did it tell the BankAccount to update with the correct amount.
	 *
	 * @author tlorenz
	 *
	 */
	public class BankAccountMock implements IBankAccount
	{

		public var updateWithAmountWasCalled : Boolean;
		public var updateWithAmountWasCalledWith : Number;
		private var _isConnected : Boolean;

		public function updateWithAmount(amount : Number) : void
		{
			updateWithAmountWasCalled = true;
			updateWithAmountWasCalledWith = amount;
		}

		public function get isConnected() : Boolean
		{
			return _isConnected;
		}

		public function set isConnected(value : Boolean) : void
		{
			_isConnected = value;
		}
	}
}

Notice, that we added a setter for the isConnected property (not required by the interface), in order control the state of the bank account in our tests.

The Employee tests

Make sure to read the comments inside the following snippet, to get an idea of how it works and why we end up with these long test names.


package traditional.tests.accounting.company
{
	import accounting.company.Employee;
	import accounting.interfaces.IEmployee;
	import org.flexunit.assertThat;
	import org.flexunit.asserts.*;
	import org.hamcrest.object.equalTo;
	import traditional.fakes.BankAccountMock;

	/**
	 *
	 * @author tlorenz
	 * quoting from this nice tutorial (with comments added):
	 * http://www.insideria.com/2009/05/flashbuilder4-will-support-fle.html
	 *
	 * •	[Before] - Replaces the setup() method in FlexUnit 1 and allow using multiple methods. Supports async, timeout, order and ui attributes.
	 * 					- is executed before each test
	 *
	 * •	[After] - Replaces the teardown() method in FlexUnit 1 and allow using multiple methods. Supports async, timeout, order and ui attributes
	 * 					- is executed after each test
	 * 					- good unit tests should not need to undo anything after a test
	 *
	 * •	[BeforeClass] - allow running methods before test class. Supports order attribute.
	 * 					- is executed once before all tests
	 *
	 * •	[AfterClass] - allow running methods after test class. Supports order attribute.
	 * 					- is executed once after all tests
	 * 					- good unit tests should not need to undo anything after a test
	 */
	public class EmployeeTests
	{

		// This will be injected instead of a real BankAccount and allows verification of interaction.
		private var _bankAccount_Mock : BankAccountMock;

		// _sut is System Under Test (what I am testing - in this case Employee)
		// this naming convention is taken from the in depth book: "xUnit Test Patterns: Refactoring Test Code"
		private var _sut : IEmployee;

		[Before]
		public function setup() : void
		{
			_bankAccount_Mock = new BankAccountMock();
			_sut = new Employee(_bankAccount_Mock);
		}

		[Test]
		public function Initially_WasPaidIsFalse() : void
		{
			assertFalse(_sut.wasPaid);
		}

		[Test]
		public function Initially_WasFiredIsFalse() : void
		{
			assertFalse(_sut.wasFired);
		}

		[Test]
		public function Initially_SalaryIsZero() : void
		{
			assertThat(_sut.salary, equalTo(0));
		}

		[Test]
		public function WhenToldThatHeIsFired_WasFiredIsTrue() : void
		{
			_sut.fire();

			assertTrue(_sut.wasFired);
		}

		// The following Testnames follow the pattern suggested by Roy Osherove in his "The Art of UnitTesting"
		//
		// [Test]
		// public function Situation_Action_Result
		//
		// As can be seen further down, this can result in very verbose testnames when the situation is a little complex

		[Test]
		public function BankAccountIsConnected_Paying_SetsWasPaidToTrue() : void
		{
			_bankAccount_Mock.isConnected = true;

			_sut.pay();

			assertTrue(_sut.wasPaid);
		}

		[Test]
		public function BankAccountIsConnected_Paying_UpdatesBankAccount() : void
		{
			_bankAccount_Mock.isConnected = true;

			_sut.pay();

			assertTrue(_bankAccount_Mock.updateWithAmountWasCalled);
		}

		[Test]
		public function BankAccountIsNotConnected_Paying_SetsWasPaidToFalse() : void
		{
			_bankAccount_Mock.isConnected = false;

			_sut.pay();

			assertFalse(_sut.wasPaid);
		}

		[Test]
		public function BankAccountIsNotConnected_Paying_DoesNotUpdateBankAccount() : void
		{
			_bankAccount_Mock.isConnected = false;

			_sut.pay();

			assertFalse(_bankAccount_Mock.updateWithAmountWasCalled);
		}

		[Test]
		public function SalaryIs1DollarAndBankAccountIsConnected_Paying_UpdatesBankAccountWith1Dollar() : void
		{
			const SALARY : Number = 1;

			_sut.salary = SALARY;

			_bankAccount_Mock.isConnected = true;

			_sut.pay();

			assertThat(_bankAccount_Mock.updateWithAmountWasCalledWith, equalTo(SALARY));
		}

		[Test]
		public function SalaryIs2DollarsAndBankAccountIsConnected_Paying_UpdatesBankAccountWith2Dollars() : void
		{
			const SALARY : Number = 2;

			_sut.salary = SALARY;

			_bankAccount_Mock.isConnected = true;

			_sut.pay();

			assertThat(_bankAccount_Mock.updateWithAmountWasCalledWith, equalTo(SALARY));
		}
	}
}

Running these tests produces the following output:

The long test names are necessary for us to know right away what went wrong when a  test fails. Ideally we don’t even want to look at the test code, but go right to the production code to fix the problem.

Obviously a test name like “testingWasFired” won’t accomplish that goal. Instead we include the Context (Situation), what we did (Act/Because) and what result we were expecting. In BDD terms the test name takes on this format: Context_Because_ExpectedResult. This maps to the TDD naming: Situtaion_Action_ExpectedResult.

From looking at some of the resulting test names like:

SalaryIs1DollarAndBankAccountIsConnected_Paying_UpdatesBankAccountWith1Dollar

or as found inside the Employer tests (part of the downloadable source code):

EmployeeWasNotFiredSalaryIsGreaterZeroAndWasNotPaid_PayEmployee_PaysHim


we can see that this could become a problem once we are dealing with more complex contexts and/or actions.

Note: the first 4 tests names deviate from this pattern, as there is either no context to be setup and/or nothing is done to the system under test.

Lets look closer at one of the Employee tests to show the naming convention in an example:


                [Test]
		public function BankAccountIsConnected_Paying_SetsWasPaidToTrue() : void
		{
			_bankAccount_Mock.isConnected = true;

			_sut.pay();

			assertTrue(_sut.wasPaid);
		}

The test name has the three parts which correspond to a line of code each:

  • Context:                              BankAccountIsConnected     _bankAccount_Mock.isConnected = true;
  • Because:                             Paying                                             _sut.pay();
  • Expected Result:             SetsWasPaidToTrue                  assertTrue(_sut.wasPaid);

Test maintenance

There is one more very important disadvantage to the described approach.

Once  the functionality of the system under test grows, we need to prepare more things in order to run our tests. The problem is though, that preparations needed by test2 may not be necessary for test1 and vice versa.

As a result if we setup everything that is needed by either test inside the [Before] method, it becomes hard to see which preparation each test actually needs, which makes troubleshooting a failed test that much harder.

If , on the other hand, we put these individual preparations inside the test methods itself, we will pay for it later, when the class under test changes.

For example lets say 10 tests setup the system under test  by calling a method on it that takes currently one parameter. If we now change it to take another (non-optional) parameter, we have to change the invocation in all these 10 tests.

This can become a huge problem and make changes to our code a lengthy process, which is of course not desirable in an agile environment.

Another way around these is to factor out the commonalities into separate methods e.g. prepareInvocatorTests(). At this point though it becomes difficult again to understand each test because in order to do so, we have to navigate to an extra method.

On the other hand, there are clear advantages to writing our tests in this way, as I will mention inside the summary section which will close the discussion of each of the three different approaches I am presenting.

Summary

Advantages

  • easier to understand for people new to BDD/ TDD than the approaches shown next
  • fully integrates with FlexUnit and doesn’t require the creation of custom test runners
  • all tests for a class are contained inside one ActionScript file

Disadvantages

  • verbose test names can become a problem
  • when contexts become more complex and need to be different for each test, they either become less readable or a maintenance nightmare

We’ll take a first stab at solving these issues in the next part.

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: