Tuesday, February 3, 2015

TDDing a unit, not a class

Some time ago, I blogged about "Unit tests vs Class tests", where I described how it differs when you think of a unit more as a collection of classes instead of a single class.

How can we TDD such a unit?

This reddit thread triggered me to explain it a bit more. The title of the thread was interesting: "How does one TDD a user and session model?"

Such question is a good example, how the class tests seem to be the default in the "unit testing" thinking. Instead of asking how to TDD the Authentication unit, we're asked how to TDD some single classes - which often depend on each other.

Here is my response:

One way to do it is to think one level higher. Don't focus on the User and Session classes, but focus on the whole feature/module that you're building. In this case, that's probably Authentication, right?

Think of Authentication as a class, which is a facade. It's very likely, that you will come up with User and Session classes, sooner or later. They will be more like implementation details.

You probably need to implement functions like:
  • register
  • logging in
  • logging out
  • change_password
  • change_email
  • change_login
  • remember_me
  • delete_user

Together they can make nice stories. You can start with a simple test like:
def test_wrong_credentials_dont_authenticate 
  authentication = Authentication.new 
  authentication.register_user("user", "password") 
  assert_raises(WrongCredentials) { authentication.authenticate("user", "wrong password") } 
end 


(if you don't like exceptions, you can turn it into returning true/false instead)

One by one, you could build the whole feature this way, while being test-driven all the time.

In a Rails app, you will then use this Authentication object from the Rails controllers. This is also a good way of improving Rails controllers.

This approach is sometimes called top-down. You start with the expectation from the higher module (Authentication) and this drives you to implement the lower-level classes like User and Session.

Another approach is called bottom-up, where you start with the User and Session classes from the beginning and don't worry with the wrapping module, yet.

The different approaches may result in different tests. With the top-down approach you end up with module tests, while with bottom-up most likely you end up with a test per class.

1 comment:

Taha Shashtari said...

Thanks for the post! I think this approach is great especially when we think about it as an app service. So in your example, we can simply think of the Authentication class as a service that provides the needed behavior for user authentication, which in turn leads to easier testing.

However, an alternative approach we can use to test this feature is by using another level of testing, in this case it can be a functional test (or acceptance test if you want). Having said that, it doesn't mean we should always prefer it over unit testing, after all unit tests are more specific in case of errors, and faster to execute. But it's still a choice to consider.

Do you agree on this? Want to hear your opinion on that.