Some people ask me how Test Driven Development fits in MVC Frameworks like Django, Ruby on Rails, and all this kind of frameworks that use convention over configuration, and offer powerful tools for CRUD (Create-Read-Update-Delete) operations.
Well, the solution always depends on the kind of problem you face. If all your application needs to do is CRUD, then, the framework is perfect. You don't need TDD at all. You just write some entities (known as models) and let the framework create all kinds of scaffolding to let the user create and retrieve data. Everything is created for you automatically.
You even don't need system tests because the framework has already been tested.

However, none of my applications are so simple. They go beyond CRUD. They got business logic which I need to test drive.

The big mistake in my opinion is to clutter the controller with the business logic. In the development of, we never have business logic within a controller. The mission of the controller, is to get the input from the user, call the application core, get the response, render a view and send it back to the user. The controller should not even manage entities (models if you want). It just gets inputs and send outputs.

Web MVC and TDD
Web MVC and TDD

If you test drive your code, you manage the dependencies using injection. Creating a business class might not be easy. For that reason we use factories. The factory might use an IoC container in order to get instances. In the case of languages like Python or Ruby, I believe that IoC container is not necessary, so we just use static factory methods (functions defined in the global namespace of a module).

The code of an action in the controller should be something as simple as this:

  2. @render_error_if_method_is_not("POST")
  3. @login_required()
  4. def save_new_profession(request):
  6. profs = request.POST.getlist(dom_constants.PROFESSION)
  7. info = __get_basic_info(request)
  8. user = info['user']
  9. factory.get_user_updater().add_professions(
  10. user, profs)
  11. return HttpResponseRedirect(reverse(my_professions))

The "add_professions" method is implemented regardless the MVC framework. It can run in the console. We test drive it with the xUnit framework as easy as always.
The common mistake people make is to implement that method in the controller itself, making it hard to reuse, develop and test.

The controller might still contain defects. For that reason we write one test for every action (controller method), using WebDriver. But we don't test all the cases through the controller, just one case. Other cases were designed with TDD even before writing the controller.

MVC frameworks are great but they are dangerous when we want them to solve all our problems. Their mission is to help with plumbing, help avoid writing same tools once again.

For those starting with Django, it is worth noting that names are confusing. They call Views what should be named Controllers, and they call Templates, what really are Views. Eventually, they call Models, what should be entities, but that is something everybody does.

  • Abel Muiño

    Great advice regarding not having business logic on controllers!

    But even then… I can’t recommend the “one test only” policy for controllers,

    For instance, if an action requires authentication, I test that. Even if the framework provides something like “@login_required()”. Why?
    Two reasons:
    a) I don’t want anybody to forget adding that
    b) When I need to refactor, I want that level of confidence

    More examples of controller tests… checking the rendered response contains all the required information (I might be able to test the views only, but Rails -which I am using lately- makes it simpler to test them through the controllers). This should include also the error case… I want to make sure that the user gets a meaningful explanation of what when wrong, but I’m usually too lazy for that.

  • Carlos Ble

    My experience is that writing those tests for every controller is too expensive. Not only writing but maintaining them.
    I prefer other techniques. For example, if all the controllers have to have that decorator, I would write a single test that inspect all the code and assert that for every controller.

    The QA team can never be replaced, manual testing is still very important. We prefer to test the app manually for 1 hour before release to production to make sure everything is fine beyond the automated tests.
    So there is a point where automated tests don’t pay their cost and we prefer manual testing.

  • Pingback: ¿Está mi código bien hecho? « El blog de Carlos Ble()

  • Pingback: TDD/BDD, Architecture and Frameworks « El blog de Carlos Ble()