2

I want to write a class A that has a method calculate(<params>). That method should calculate a value using database data. So I wrote a class Test_A for unit testing (TDD). The database access is done using another class which I have mocked with a class, let's call it Accessor_Mockup.

Now, the TDD cycle requires me to add a test that fails and make the simplest changes to A so that the test passes. So I add data to Accessor_Mockup and call A.calculate with appropriate parameters.

But why should A use the accessor class at all? It would be simpler (!) if the class just "knows" the values it could retrieve from the database. For every test I write I could introduce such a new value (or an if-branch or whatever).

But wait ... TDD is more. There is the refactoring part. I could refactor class A using the DB accessor. But I could refactor it in any other way as well (e.g. introducing new classes that encapsulate the data). So, again: How do I write tests that force A to use the database (and nothing else)?

5 Answers 5

11

If you just wrote calculate() without requiring it to use the database, it would need to get its data from somewhere else. I know, that sounds trivially simple and boring, but please bear with me. So write that. Let's say that "somewhere else" is most naturally a new parameter or two. Now you can test to your heart's delight without using a real database or mocking one. And now you have a clean, independent, nicely decoupled unit. That's good design. And it's test-driven design. You can call your new method from a class that does have database access, and feed it from the database. But testing that interaction will be less arduous, because you already trust - even know - that the calculate() unit is working correctly. So all the testing you need to do with your database-associated calculation is that it's feeding the right values to your well-tested calculate() method.

That is separation of concerns and that is good design. You started with an assumption about your design: that calculate() would entail both the concern of database access and the concern of calculation. That's a somewhat muddy assumption, a somewhat weak design. And the difficulty you have in testing under that assumption reveals better design. That is TDD.

1
  • I think that the people working with you learn a lot from you. Commented Jul 4, 2013 at 11:02
9

But why should A use the accessor class at all?

Well, why should you use the database at all? Because the database can change.

Test that calculate(A) returns the expected value, then change the accessor and verify that calculate(A) returns the new expected value.

It's test driven design. You know what you want (a database), then you add tests that drives your coding in the direction of that ideal.

And for the other part, where you just add more and more if-statements to cover all parameters you test. Yes, that is what you should do: until you think that adding another if-statement is more complicated than actually doing the (least amount of) work you actually need to.

I'd say typical programmer should hit that limit before writing four if-statements.

The basic idea with "simplest" is to never get stuck on any single unit-test: if you know the end result you want, return that; think of another result you want and add that test. Repeat. Sooner or later you'll figure out a more efficient/simple way to do it.

Edit: How do I write tests that force A to use the database (and nothing else)?

The flippant answer is, you can't. From the perspective of the test, the method is a black box: you give it input and verify the output; whether it gets that output by querying the database or scrying is not something the test is concerned with.

In practice though: the database, or mock-database is just another input. Vary the input and verify the output; repeat until you can think of no more failing tests. It should not be long before the simplest thing you can do is actually query the (mock-)database.

3
  • Agreed so far. What I still do not get is why should I do the refactoring in a specific way? Using the DB feels more like introducing a new feature - which should be done (as you said) test driven. I can imagine other ways to refactor class A without using the DB. And I cannot drive to use the DB because refactoring means that the test methods do not change. Maybe I should point this out more clearly in the original question. Commented Jul 3, 2013 at 11:12
  • 1
    @TobiMcNamobi As far as I know there is nothing in TDD that says anything about how the refactoring should be done. Usually you refactor for clean code, but nothing says you can't refactor for performance or whatever. Introducing a database isn't something you'd usually do as refactoring. I guess I just don't understand what the question is.
    – Odalrick
    Commented Jul 3, 2013 at 12:12
  • 2
    At some point you just have to switch on your brain. If you could just add test cases until the inner code suddenly becomes perfect due to some magic refactoring rules, the computer would write the implementation and we could just write tests. Commented Jul 3, 2013 at 14:58
2

You shouldn't write tests that Force class A to use the database. What you can write are tests which expect that the class calls the Accessor class. How the Accessor class gets its data is up to it, class A is just expecting something back.

It seems to me as though you've come up against the same problem I did when I first started using TDD, thinking all the way along a chain. You write unit tests for each link in the chain. I hope this analogy makes sense.

1

But why should A use the accessor class at all? It would be simpler (!) if the class just "knows" the values it could retrieve from the database. For every test I write I could introduce such a new value (or an if-branch or whatever).

TDD won't work if your aim is to trick the tests when writing the productive code. An if cascade for all parameter combination used in the test is not a reasonable implementation – the tests only need to ensure that any simple, non-malicious implementation is correct.

So in your database example, just make sure that you pass in enough data via the accessor stub so that any (lazy) programmer will implement a solution that uses that data, instead of making up the answers.

0

TDD is not about enforcing a specific implementation, a specific algorithm or a specific internal design on your functions.

It just enforces a given signature and a specific input/output behaviour of your functions to some test cases. It will guide you also to decouple your functions from external resources / dependencies. No less, no more. How you build your things internally is still up to you. And if you are going to implement a function specficially in a way that it fulfills all unit tests, but will immediately break in production because it cannot work together with a database, then TDD does not prevent that.

For implementing a real-world, production-ready program, you will have to care using different instruments like

  • problem solving
  • algorithm design
  • making architectural decisions
  • make decisions about a specfic implementation
  • integration tests
  • acceptance testing

These are things TDD cannot really help you with.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.