Date Archives

December 2014

Booked Feedback

One of the things I love most about working on open source software is the engagement of the community. Booked has a very active community with a huge repository of information.

An increasingly common problem I’m hearing is that Booked has a steep learning curve. Being in the application every day, I have blinders on that hide these types of issues from me.

How can we clean up the workflow and make Booked a pleasure to use? I want to know what’s difficult. I want to hear your thoughts on how to improve the initial application experience.

My goal is to get to a point where new users do not need a help page or a tutorial. I want you to go from install to booking a reservation in minutes without needing help from the community.

Submit your feature requests and improvement suggestions!

I can’t promise that every suggestion will be implemented, but I can promise to consider each suggestion and use your feedback to improve Booked.

TDD: Back to Hand Rolled Stubs

I’m unashamedly an Agile practitioner and self-proclaimed enthusiast. It’s not a perfect way to build software, but I haven’t found anything better. And until I do, this is the approach I’m taking.

Building Quality In

One of the core principles of Agile is the focus of building quality in from the start, not asserting quality after the product is built. At first this is counter-intuitive, right? How can you ensure the quality of something which is in the process of being built? This is where TDD saves the day. Write your tests first – set the expectations of what the code is supposed to do – then write the production code.

When I rewrote Booked for the 2.0 release I did it all test-first. In fact, there are thousands of assertions covering the majority of the code base. This is super important for a dynamic language like PHP where it is very easy to shoot yourself in the foot. Lots of tests = high confidence that my change didn’t break something. Of course, unit tests in a statically typed language are also critical.

The Unit Testing Approach

I use PHPUnit in Booked and it works great. Aside from providing your typical unit testing functionality, PHPUnit also includes test doubles. Mocks and stubs are great for substituting behavior and isolating components in your tests. It’s a technique I rely on heavily and it leads to loosely coupled code and more focused tests.

Martin Fowler has a great writeup comparing mocks, stubs and other substitution patterns.

Death By Over-Specification

When I first starting practicing TDD there weren’t a lot of mocking frameworks or libraries available. We wrote our stubs and mocks by hand and used a plain assertion framework (nUnit in the .NET space). It takes some time to write this code – you’re building new objects to stand in for existing objects. The interfaces need to match, the return types need to match, and in some cases the return object needs to be set up in a certain way. It can be a decent amount of work. My laziness is what led me to mocking frameworks.

Mocking libraries gained a lot of traction around 2006/2007. Mocks, and mocking frameworks in particular, work by setting up expectations and then returning appropriate responses when you exercise the code. This awesome because you can quickly substitute behavior of specific methods without building up a whole object.

Great! Look how fast I’m going!

So Super Fast

One problem with mocks is that they want to know everything about your code. For example, given you pass parameters x, y, z to this function then return this specific result. This is great when you care about how you execute a function – and there are valid reasons to care about that. This is not great when all you want to do is return a canned response and move on. One of the most frequent arguments I hear against unit testing and TDD is that the tests are fragile and difficult to maintain. Over-specification is generally the real cause of these problems.

An Example

Here’s a test pulled straight from the Booked test suite.

public function testBindsDefaultScheduleByMonthWhenNothingSelected()
{
	$this->page = $this->getMock('ICalendarPage');
	$this->repository = $this->getMock('IReservationViewRepository');
	$this->scheduleRepository = $this->getMock('IScheduleRepository');
	$this->calendarFactory = $this->getMock('ICalendarFactory');
	$this->resourceService = $this->getMock('IResourceService');
	$this->subscriptionService = $this->getMock('ICalendarSubscriptionService');
	$this->privacyFilter = new FakePrivacyFilter();

	$this->presenter = new CalendarPresenter(
		$this->page,
		$this->calendarFactory,
		$this->repository,
		$this->scheduleRepository,
		$this->resourceService,
		$this->subscriptionService,
		$this->privacyFilter);

	$showInaccessible = true;
	$this->fakeConfig->SetSectionKey(ConfigSection::SCHEDULE, ConfigKeys::SCHEDULE_SHOW_INACCESSIBLE_RESOURCES, 'true');

	$userId = $this->fakeUser->UserId;
	$defaultScheduleId = 10;
	$userTimezone = "America/New_York";

	$calendarType = CalendarTypes::Month;

	$requestedDay = 4;
	$requestedMonth = 3;
	$requestedYear = 2011;

	$month = new CalendarMonth($requestedMonth, $requestedYear, $userTimezone);

	$startDate = Date::Parse('2011-01-01', 'UTC');
	$endDate = Date::Parse('2011-01-02', 'UTC');
	$summary = 'foo summary';
	$resourceId = 3;
	$fname = 'fname';
	$lname = 'lname';
	$referenceNumber = 'refnum';
	$resourceName = 'resource name';

	$res = new ReservationItemView($referenceNumber, $startDate, $endDate, 'resource name', $resourceId, 1, null, null, $summary, null, $fname, $lname, $userId);

	$r1 = new FakeBookableResource(1, 'dude1');
	$r2 = new FakeBookableResource($resourceId, $resourceName);

	$reservations = array($res);
	$resources = array($r1, $r2);
	/** @var Schedule[] $schedules */
	$schedules = array(new Schedule(1, null, false, 2, null), new Schedule($defaultScheduleId, null, true, 3, null),);

	$this->scheduleRepository
			->expects($this->atLeastOnce())
			->method('GetAll')
			->will($this->returnValue($schedules));

	$this->resourceService
			->expects($this->atLeastOnce())
			->method('GetAllResources')
			->with($this->equalTo($showInaccessible), $this->equalTo($this->fakeUser))
			->will($this->returnValue($resources));

	$this->resourceService
			->expects($this->atLeastOnce())
			->method('GetResourceGroups')
			->with($this->equalTo(null), $this->equalTo($this->fakeUser))
			->will($this->returnValue(new ResourceGroupTree()));

	$this->page
			->expects($this->atLeastOnce())
			->method('GetScheduleId')
			->will($this->returnValue(null));

	$this->page
			->expects($this->atLeastOnce())
			->method('GetResourceId')
			->will($this->returnValue(null));

	$this->repository
			->expects($this->atLeastOnce())
			->method('GetReservationList')
			->with($this->equalTo($month->FirstDay()),
				   $this->equalTo($month->LastDay()->AddDays(1)),
				   $this->equalTo(null), $this->equalTo(null),
				   $this->equalTo(null), $this->equalTo(null))
			->will($this->returnValue($reservations));

	$this->page
			->expects($this->atLeastOnce())
			->method('GetCalendarType')
			->will($this->returnValue($calendarType));

	$this->page
			->expects($this->atLeastOnce())
			->method('GetDay')
			->will($this->returnValue($requestedDay));

	$this->page
			->expects($this->atLeastOnce())
			->method('GetMonth')
			->will($this->returnValue($requestedMonth));

	$this->page
			->expects($this->atLeastOnce())
			->method('GetYear')
			->will($this->returnValue($requestedYear));

	$this->page
			->expects($this->atLeastOnce())
			->method('SetFirstDay')
			->with($this->equalTo($schedules[1]->GetWeekdayStart()));

	$this->calendarFactory
			->expects($this->atLeastOnce())
			->method('Create')
			->with($this->equalTo($calendarType),
				   $this->equalTo($requestedYear), $this->equalTo($requestedMonth), $this->equalTo($requestedDay),
				   $this->equalTo($userTimezone))
			->will($this->returnValue($month));

	$this->page->expects($this->atLeastOnce())->method('BindCalendar')->with($this->equalTo($month));

	$details = new CalendarSubscriptionDetails(true);
	$this->subscriptionService->expects($this->once())->method('ForSchedule')->with($this->equalTo($defaultScheduleId))->will($this->returnValue($details));

	$this->page->expects($this->atLeastOnce())->method('BindSubscription')->with($this->equalTo($details));

	$calendarFilters = new CalendarFilters($schedules, $resources, null, null, new ResourceGroupTree());
	$this->page->expects($this->atLeastOnce())->method('BindFilters')->with($this->equalTo($calendarFilters));

	$this->presenter->PageLoad($this->fakeUser, $userTimezone);

	$actualReservations = $month->Reservations();

	$expectedReservations = CalendarReservation::FromScheduleReservationList($reservations,
																			 $resources,
																			 $this->fakeUser,
																			 $this->privacyFilter);

	$this->assertEquals($expectedReservations, $actualReservations);
}

This is 75 lines of mock setup code! We’re expecting specific parameters and have mock objects being returned all over. Let’s take a look at this same test with stubs.

public function testBindsDefaultScheduleByMonthWhenNothingSelected()
{
	$this->page = new StubCalendarPage();
	$this->repository = new StubReservationViewRepository();
	$this->scheduleRepository = new StubScheduleRepository();
	$this->calendarFactory = new StubCalendarFactory();
	$this->resourceService = new StubResourceService();
	$this->subscriptionService = new StubCalendarSubscriptionService();
	$this->privacyFilter = new FakePrivacyFilter();

	$this->presenter = new CalendarPresenter(
		$this->page,
		$this->calendarFactory,
		$this->repository,
		$this->scheduleRepository,
		$this->resourceService,
		$this->subscriptionService,
		$this->privacyFilter);

	$showInaccessible = true;
	$this->fakeConfig->SetSectionKey(ConfigSection::SCHEDULE, ConfigKeys::SCHEDULE_SHOW_INACCESSIBLE_RESOURCES, 'true');

	$userId = $this->fakeUser->UserId;
	$defaultScheduleId = 10;
	$userTimezone = "America/New_York";

	$calendarType = CalendarTypes::Month;

	$requestedDay = 4;
	$requestedMonth = 3;
	$requestedYear = 2011;

	$month = new CalendarMonth($requestedMonth, $requestedYear, $userTimezone);

	$startDate = Date::Parse('2011-01-01', 'UTC');
	$endDate = Date::Parse('2011-01-02', 'UTC');
	$summary = 'foo summary';
	$resourceId = 3;
	$fname = 'fname';
	$lname = 'lname';
	$referenceNumber = 'refnum';
	$resourceName = 'resource name';

	$res = new ReservationItemView($referenceNumber, $startDate, $endDate, 'resource name', $resourceId, 1, null, null, $summary, null, $fname, $lname, $userId);

	$r1 = new FakeBookableResource(1, 'dude1');
	$r2 = new FakeBookableResource($resourceId, $resourceName);

	$reservations = array($res);
	$resources = array($r1, $r2);
	/** @var Schedule[] $schedules */
	$schedules = array(new Schedule(1, null, false, 2, null), new Schedule($defaultScheduleId, null, true, 3, null),);

	$this->scheduleRepository->SetSchedules($schedules);

	$this->resourceService->SetResources($resources);

	$this->page->SetScheduleId(null);

	$this->page->SetResourceId(null);

	$this->repository->SetReservaions($reservations);

	$this->page->SetCalendarType($calendarType);

	$this->page->SetDate($requestedDay, $requestedMonth, $requestedYear);

	$this->page->SetFirstDayStart($schedules[1]->GetWeekdayStart());

	$this->calendarFactory->SetCalendarMonth($month);

	$details = new CalendarSubscriptionDetails(true);
	$this->subscriptionService->SetDetails($details);

	$this->presenter->PageLoad($this->fakeUser, $userTimezone);

	$actualReservations = $month->Reservations();

	$expectedReservations = CalendarReservation::FromScheduleReservationList($reservations,
																			 $resources,
																			 $this->fakeUser,
																			 $this->privacyFilter);

	$this->assertEquals($expectedReservations, $actualReservations);
	$this->assertEquals($month->FirstDay(), $this->repository->_SearchStart);
	$this->assertEquals($month->LastDay()->AddDays(1), $this->repository->_SearchEnd);
	$this->assertEquals($calendarType, $this->calendarFactory->_CalendarType);
	$this->assertEquals($requestedYear, $this->calendarFactory->_CreateYear);
	$this->assertEquals($requestedMonth, $this->calendarFactory->_CreateMonth);
	$this->assertEquals($requestedDay, $this->calendarFactory->_CreateDay);
	$this->assertEquals($userTimezone, $this->calendarFactory->_CreateTimezone);
	$this->assertEquals($month, $this->page->_BoundMonth);
	$this->assertEquals($details, $this->page->_BoundSubscription);
	$calendarFilters = new CalendarFilters($schedules, $resources, null, null, new ResourceGroupTree());
	$this->assertEquals($calendarFilters, $this->page->_BoundFlters);
	$this->assertEquals($this->subscriptionService, $this->subscriptionService->_ScheduleId);
}

This is still a huge test (that’s a problem for another post). Here the stub setup code gets cut down to about 15 lines. What I also love is that I don’t have expectations set in the mock specification. Any assertions around parameters have been moved down with the rest of the assertions. This is wonderful for a few reasons.

1 – This follows the Arrange-Act-Assert model that helps us in writing clean tests.
2 – Most of the time I don’t care about what parameters I pass to a method.
3 – Mocks will typically not return anything if you don’t pass the right value to a mocked-out function. When you pass the wrong value null is returned, causing a null reference exception in your code. This is very frustrating and difficult to trace, especially when you changed the parameters intentionally.

I don’t care which method is called (most of the time). Often times I don’t care about the data that we’re feeding to a collaborating object, either. I just want to test this specific object’s functionality and assume that everything else is tested elsewhere and works as it should.

The Stub Library

One of the biggest ancillary benefits I’ve found to hand writing my stubs is that I’ve built up a pretty solid library of reusable objects. I have stub objects ready to go for different pages, for reservations, data access and so on. I can pull in an existing stub and continue on with my test. I don’t need to set up the same stub expectation or response over and over. I don’t even have to think about how this collaborating object works. And if I apply an automated refactor to a method signature I don’t have to worry about breaking a ton of tests – the refactoring would apply to the stub object, as well.

Looking Forward

I’ve come full circle on stubs since my early days of TDD. I used to view them as a productivity killer, but my opinion now is that they actually save me time. Sure, there’s a little upfront investment, but the return on that investment is high. Who knows, maybe I’ll venture back into the land of mocks again one day. Until then, I’m happy with my well-tested, easy to maintain production code base.

Twinkle Toes Software – Looking back on my first year

Wow, it’s been a year since I founded Twinkle Toes Software. I thought this would be a good time for me to stop and reflect a bit on how its going and what I’ve learned.

The Zero Entry Pool Approach

Am I a “zero-entry” entrepreneur? I’m sure there are countless ways to approach starting a business. For me, I decided against diving head first into it and instead opted for what I’m calling a “zero-entry” approach.

Have you been in a zero-entry pool?

Zero Entry Pool

This has a gradually declining entrance that allows you to enter the pool at your own pace. Compare this to a diving board, where you have very little control over the speed at which you enter – and there’s no turning back. I’ve taken the former approach to launching Twinkle Toes Software. At this point I’m about waist-deep 🙂 And this is what I’ve learned.

So… How’s It Going?

Thanks for asking! It’s going well. Interest in Booked Hosting is strong, as is the interest in custom development. Surprisingly, there hasn’t been much interest in our enterprise support services. Our community support is incredible, so I guess I shouldn’t be all that bewildered.

What I’ve Learned – Get Your Ducks In a Row

If you’re going to start a business, whether it’s a sole proprietorship (like Twinkle Toes), an LLC, S Corp, whatever, get those ducks in a row.

Ducks in a row

The Legal Stuff

First of all, I’m not a lawyer and this isn’t legal advice, so consult an attorney for all legal matters. Every state and business type has their own set of rules. Find out what yours are and follow them. Better yet, hire an attorney to help you or use a site like LegalZoom (I don’t use LegalZoom and have no affiliation with them). There are different advantages and rules that go into different business registration types. Find the one that makes sense for you.

The Small Business Association here in the US has a lot of great advice and tools for launching your business. Definitely check that out.

Get your company registered with your state, city and county municipalities. Get an EIN and keep your social security number out of all records. Keep your personal and professional identities separate – your business is one thing and your personal life is another. The more you keep these isolated from one another, the better.

Make sure you have the right insurance and make sure you have enough of it. If you keep your business legally separate it can be difficult to jeopardize your personal finances in the event that legal action is taken against you. That said, you want to ensure you have insurance that offers the right amount of protection for your situation.

Set Up a Professional Web Presence

If you have a website that looks like it was built by a kid you will miss out on business. Really, would you give your money to a business that does not present themselves in a professional way?

Unless you’re a web designer, don’t waste your time building your own web site. Joomla and WordPress are readily available and there are thousands of themes available, most at a nominal cost. Set it up and spend your time focusing on content and marketing rather than design.

Ongoing Operations

Keep Track of Every Dollar Coming In and Going Out

Keep business expenses and income separate from your personal accounts. Set up a separate bank account if you want to be safe. Taxes are not fun. Taxes are even less fun when you’re reporting for your business, too. Hire an accountant if you are pulling in enough money to make it worth the cost. Otherwise, use an accounting package. I’ve heard good things about QuickBooks, but I’ve never used the software.

I do use Wave and would highly recommend them. The software is web-based, so it is available anywhere. This is great when you’re not in front of your primary computer or need to capture transactions on the go. They provide quoting, invoicing, accounting, credit card payment processing and more. Amazingly, it’s also free (aside from the credit card processing, which runs at a reasonable 3%).

Different business types have different accounting rules. Know what those rules are and follow them. If you’re going all in and will be self-employed make sure you know when and how to pay your taxes. Actually, just hire an accountant. My guess is that you’re not a tax or finance expert so find someone that is.

Analyze Your Web Traffic

The more you understand your website’s audience, the more effectively you can tune your website. Google Analytics makes it trivial to capture and analyze your web traffic. See where people are coming from, their navigation path, how long they stick around, etc. This is crucial information to help keep an eye on the effectiveness of any marketing campaigns or call to action sections of your website.

It’s Hard Work

Take That Estimate and Double It

I don’t know anyone who started their own business thinking it would be easy. I believed it would be a lot of work, but I was wrong. I underestimated how much work it is. Be prepared to be working long days. Set these expectations with your family. You absolutely need their support if you’re going to be successful.

Also, set realistic timeline expectations with clients. Don’t promise to turn something around in a few days when you know you need weeks with your current workload. You can attract a lot of clients by promising quick turnarounds, but you’re going to lose them and your reputation will be tarnished if you can’t keep your promises. In my experience most clients want things delivered immediately, but understand and accept longer estimates. Be honest and deliver and you’ll build a loyal client base.

You Will Probably Lose Money

There are very few business that launch and immediately turn a profit. Be prepared to lose money for months, maybe even years. You may never make money. Something like half of all new businesses fail in the first few years.

What does this mean?

If you’re quitting your full-time gig for this then have enough in your savings to live off of for at least a year. If you’re doing this part-time then be ready to invest a lot of your free time and money. Find investors if necessary.

Booked was in the marketplace for 10 years and had a reputation as a successful open source product. Even with that history, I’m barely turning a profit. I lost money during the first 6-7 months after the Twinkle Toes launch.

But It’s Fun

I’m having a fantastic time running Twinkle Toes. Yes, it’s a lot of work but it’s work that I am passionate about. Booked Scheduler is very close to my heart and being able to dedicate time to it while making a little bit of money feels great.

What Did I Miss?

Anyone have any other tips? Let’s hear about your experiences launching a business or product!