The TDD/BDD Religion, Getting The

I must admit that I’m fully drunk on the Kool-Ade.

The tl;dr : testing isn’t just writing tests, but also using mock objects and services to simplify testing, and using package managers to port code to new versions of modules. It takes days to learn how, but it’s worth it.

I used to think of testing as a great way to refactor code. If I had to refactor, I’d write tests, and then refactor, and it generally worked. Other times, I had to write something that was hard to set up, like communication with a server, and I’d use testing frameworks to allow me to perform the setup repeatedly.

When I was doing a lot of PHP and VBA work, testing seemed optional. Then I moved to a Django + Angular JS stack, and it became more important. I also had a WordPress project and it helped to be able to write tests for that.

Django and Angular are both more “OO” or at least more modular, so testing was easier to add. I also needed the testing because I had less experience with both. WP was less OO (if at all) but I was writing OO code, and executing the code through the browser was just too much of a pain in the ass.

I hadn’t really taken the plunge, mainly because there’s a pretty steep learning curve to fully adopting the testing tools and methods. They differ for each language and framework, and take a few days to learn. In an heterogeneous environment, this can take weeks of self-education.

Tests Ease Updates

What happened to convince me the value of testing was when updates to the frameworks came out. With testing, it was far less stressful to perform an upgrade to the software platform. Yes, it broke, but you could get a list of errors, and fix them through test-driven bug-fixing.

The release cycle for Django is every six months, and features are deprecated across two releases. The releases for Angular were more sporadic, but the changes could sometimes break things. WordPress is updated all the time.

Package Managers

These frameworks are updating at a snail’s pace compared to the way Javascript is coded today. You use so many modules, via NPM, that your project may have dozens of dependencies. Each module might change, and sometimes, they deprecate features without warning. It seems like chaos.

The trick is to use tests. If you have enough tests, you’re able to update the libraries, and catch errors, and adapt to the new reality.

The same applies to using Python’s PIP, Ruby’s Gems, and now PHP’s Composer. Looking back, this was obviously how Perl’s CPAN maintained quality, but I was so clueless that I just thought the CPAN programmers were geniuses. They were geniuses – but what made things work were the tests. I’d sometimes come across Perl packages with a thousand unit tests.

A rich set of tests is how you can write code that depends on other code (which is all code). When the libraries change, you can run your tests and catch errors. You need to use code coverage tools to make sure all your code is tested, and that means writing more test code than actual program code.

Stumbling toward BDD and Mockism

Martin Fowler has a good article about “testing state” versus “testing behavior.” He leans toward state testing, as do most people. Basically, you make an object, call some methods, then look at the object to see if expected changes happened. To isolate the test, you write stub classes (aka a test double) that return constants to fake interactions with your test object. It’s simple and straightforward.

When I started plotting how to make a test double for a web service, it started to get complicated real fast. I needed to not only write a stub, it needed a server to run on, and a fake network to separate itself from the application.

The easy fix was to use Mocks. Mocks are objects that fake other objects. The problem then becomes one of returning fake data… which becomes a problem of writing up fake data.

The easy way to avoid the hassle of writing fake data is to embrace Behavior Driven Development (BDD). BDD is just like TDD, but with the intention of testing at a slightly higher level of abstraction. You test by inspecting how the system under test interacted with some mocks. The mock objects would count method calls and input values, checking if they matched expected values.

BDD seems to be good for testing code that interacts with services.

There are a lot of articles that define the difference between BDD and TDD as a different “language”, or say there’s no difference at all. My sense is that BDD and TDD test two different types of code: BDD is necessary for code that integrates disparate services, TDD is necessary to test the state within services.

I’m sure there are other situations, but I don’t know them.

BDD with Mocks requires Dependency Injection

Mocks fake other objects or classes, instrumenting them to record interactions with those objects or classes.

The problem is inserting the mocks into your running program. Chances are, you cannot. Consider this fake code:

class ClassUnderTest {
  function codeUnderTest(x) {
    fooservice = new FooService();
    return fooservice.fooMethod(x);
  }
}

Imagine that we want to test this, but don’t want to call FooService because it makes a network request. We need to make a mock. The way is like this:

  mockfoo = new Mock(fooservice);

The mock wraps the object, and can now monitor calls to the service. (Among other things, mocks log method calls.)

  return mockfoo.fooMethod(x);

We can find out how many times fooMethod was called, and with what value, thanks to the mock.

The problem is, we had to modify the codeUnderTest(). That’s not acceptable.

The fix is Dependency Injection. Basically, instead of creating the fooservice within the function, we pass it in as a parameter to the class constructor:

This code is not real testing code at all. Fake java.

class ClassUnderTest {
  FooService fooservice;
  public ClassUnderTest(FooService foo) {
    fooservice = foo;
  }
  int codeUnderTest(x) {
     return fooservice.fooMethod(x);
  }
}
// to test
FooService mockfoo = mock(FooService.class);
ClassUnderTest obj = new ClassUnderTest(mockfoo);
obj.codeUnderTest(2);
assert(mockfoo.count("fooMethod"), 1);
assert(mockfoo.calledWith("fooMethod"), 2);

Dependency injection is no big deal. We’ve all done it at some time or other, but didn’t call it Dependency Injection. All it means is that construction of a service doesn’t happen within a class’ code; the service object is passed into the object at runtime.

This mock object, mockfoo, just counts the calls and arguments. We could test the values, but the point of the example is to show that we can observe interactions.

So, are Mocks and BDD better?

That’s hard to say. For testing regular classes, I think mocks and BDD are not any better. For testing network based services, using mocks is a must, and testing in the BDD style is correct enough. A test written with mocks and BDD can be coded in less than an hour, maybe less than 15 minutes.

In comparison, faking a network service would take more than an hour of effort, because it’s just a lot harder and more error prone. It’s no longer a “unit test”. It’s also more likely to fail mysteriously, making it a pretty bad test.

Testable Code

The dependency injection above is an example of how we made the code “testable”. Is this better code?

Again, hard to say. The complexity increased a little, and readability decreased a little. The coupling between classes decreased a lot, and now it’s testable.