10

I'm trying to grasp what's the idea behind TDD and how a team is supposed to work with it. I have the following test case with NUnit + Moq (just writing by memory, it is not assured the example compiles but it should be explanatory):

[Test]
public void WhenUserLogsCorrectlyIsRedirectedToLoginCorrectView() {
    Mock<IUserDatabaseRepository> repoMock = new Mock<IUserDatabaseRepository>();
    repoMock.Setup(m => m.GetUser(It.IsAny())).Returns(new User { Name = "Peter" });        

    Mock<ILoginHelper> loginHelperMock = new Mock<ILoginHelper>();
    loginHelperMock.Setup(m => m.Login(It.IsAny(), It.IsAny())).Returns(true);
    Mock<IViewModelFactory> factoryMock = new Mock<IViewModelFactory>();
    factoryMock.Setup(m => m.CreateViewModel()).Returns(new LoginViewModel());

    AccountController controller = new AccountController(repoMock.Object, loginHelperMock.Object, factoryMock.Object)

    var result = controller.Index(username : "Peter", password: "whatever");

    Assert.AreEqual(result.Model.Username, "Peter");
}

AccountController has 3 dependencies which I mock that, when orchestrated inside the controller, allows me to verify if a login was correct or not.

What stumbles my mind is that... if in TDD in theory you have to write first your test suite and build your code up from it, how am I supposed to know beforehand that in order to perform my operation I'll need to use those three dependencies and that the operation will call certain operations? It's like I need to know the innards of the Subject Under Test before even implementing it in order to Mock the dependencies and isolate the class, creating some kind of write test - write code - modify test if necessary cycle.

Naturally, without any knowledge of the innards of my code and just expressing the test, I could express it like it could just need the ILoginHelper and "magically" suppose before writing the code that it will return the user on a successful login (and ultimately realize that the underlying framework doesn't work that way, for instance returning just an ID instead of the full object).

Am I understanding TDD in an incorrect way? Which is a typical TDD practice on a complex case?

Thank you

3
  • 1
    You don't have to follow strict TDD to get most benefit from unit tests.
    – Den
    Nov 25, 2014 at 9:20
  • 2
    @Den: "strict TDD" does not mean what the OP believes it means.
    – Doc Brown
    Nov 25, 2014 at 14:54
  • I recommend watching vimeo.com/album/3143213/video/71816368 (8LU: Advanced Concepts in TDD). It might help you get your head around things. Nov 28, 2014 at 7:11

6 Answers 6

21

if in TDD in theory you have to write first your test suit and build your code up from it

Here is your misunderstanding. TDD is not about writing a full test suite first - that's is a false myth. TDD means to work in small cycles,

  • writing one test at a time
  • implement only as much code as needed to make the test "green"
  • refactor (the code and the tests)

So the creation of a test suite is not done in one step, and not "before the code is written", it is interwoven with the implementation of the code in stake.

Applied to your example: you should try to start with a simple test for a controller without any dependencies (something like a prototype). Then you implement the controller, and refactor. Afterwards you either add a new test which expects your controller to do a little bit more, or you refactor/extend your existing test. Then you modify your controller until the new test becomes "green". That way you start with a simple combination of tests & subject under test, and end up with a complex test & subject under test.

When going this route, at some point in time you will find out what additional data you need as input for the controller to do its work. This can indeed happen at a moment where you try to implement a controller method, and not when designing the next test. That is the point where you stop to implement the method for a short time, and start introducing the missing dependencies first (maybe by a refactoring of your controller's constructor). This leads straightforward to a refactoring of your existing tests - in TDD, you will typically first change the tests calling the constructor, and add the new constructor attributes afterwards. And that's where coding and writing of the tests become completly entangled.

13

What stumbles my mind is that... if in TDD in theory you have to write first your test suit and build your code up from it, how am I supposed to know beforehand that in order to perform my operation I'll need to use those three dependencies and that the operation will call certain operations? It's like I need to know the innards of the Subject Under Test before even implementing it in order to Mock the dependencies and isolate the class, creating some kind of write test - write code - modify test if necessary cycle.

It feels wrong, right? And it should - not because your tests for the controller are incorrect or "bad" in any way, but because you want to test the controller before you have anything at all to be "controlled". :)

My point is: TDD will feel more natural to you once you start doing it at the level of "business rules" and "real application logic", which is also where it is the most useful. Controllers usually deal only with delegation to other components, so it is natural that, in order to test if the delegation is done correctly, you need to know to which object it is going to delegate to. The only problem is when you try to do it before you have any real logic implemented. My suggestion is that you try to implement the LoginHelper, for instance, by doing TDD in a more "behavior-oriented" way. It will feel more natural and you'll probably see more of its benefits.

So to a more generic answer: TDD is a practice with which we produce tests before writing the code we need, but it does not specifiy which kind of tests. Controllers are usually integrators of components, so you write unit tests which usually require a lot of mocking. When you write the application logic (business rules, such as placing an order, validating user authrozations, etc...), you write behavior tests, which will usually be state-based tests (given input vs. desired output). This difference is often referred to as Mockism vs. Statism by the TDD community. I am part of the (small) group that insists that both ways are correct, its just that they offer different trade-offs, thus being useful for different scenarios such as described above.

3
  • 1
    Your answer has some good points, but allow me to nitpick one thing. "Controllers are usually integrators of components, so you write integration tests, which usually require a lot of mocking" - well, I guess you probably meant "when you try to write unit tests for controllers, it will usually require a lot of mocking". IMHO the term "integration test" fits better for a test without mocking, where you actually use the real components, and no mocks, to see if they work together as intended.
    – Doc Brown
    Nov 26, 2014 at 16:02
  • Thanks @DocBrown, I was indeed referring to a "unit test that tests integration/communication between components", and not the concept of integration tests which include the real components. Nov 26, 2014 at 16:36
  • 1
    Well, now where we agree about the term "integration test", I think your answer leads straight to the next question: is it really worth to use TDD (or to write unit tests) for controllers with the primary role of "integrators"? Or should one prefer to write only integration tests for these components (maybe afterwards)?
    – Doc Brown
    Nov 26, 2014 at 17:14
4

While TDD is a test-first method, it does not require you to spend a lot of time writing test code before you write any production code.

For this example, the idea of TDD described in Kent Beck's seminal book on TDD (1) is to start with something really simple, like maybe

AccountController controller = new AccountController()

var result = controller.Index(username : "Peter", password: "whatever");

Assert.AreEqual(result.Model.Username, "Peter");

At first, you don't know everything you're going to need you to do the job. You just know you're going to need a controller with an Index method that gives you a model with a Username. You don't know how it's going to do that yet. You've just set a goal for yourself.

Then you get that to work using any means available, possibly just hardcoding the correct result at first. Then in subsequent refactorings (and even adding additional tests) you add greater sophistication one step at a time. TDD let's you to take as small of a step as you need to move forward but also leaves you free to take as large of a step as your skills and knowledge allow. By taking a short cycle between test code and production code, you get feedback on every little step you take and know with near immediacy whether what you just did worked and whether it broke any else that was working before.

Robert Martin in (2) also advocates for a very short cycle time between writing test code and writing production code.

3

You may eventually need all this complexity for a conceptually simple unit test, but you will almost certainly not write the test like this in the first place.

First of all, the complex set-up in your first six lines should be factored out into self-contained, reusable fixture code. The principles of maintainable programming apply to test code just like business code; if you use the same fixture for two or more tests, it should definitely be refactored into a separate method so that you have only one distracting line in your test, or into the class set-up code so that you have none.

But the more important thing: writing a tests first doesn't guarantee that it can stay unchanged forever. If you don't know the collaborators of a method call, you will almost certainly not be able to guess them correct on the first try. There is nothing wrong with refactoring your test code along with your business code if the public API changes. It's true that the aim of TDD is to write the correct, usable API in the first place, but this is hardly ever achieved to 100%. Requirements always change after the fact, and all too often this absolutely requires collaborators that didn't exist when you wrote the first iteration of a story. In that case there's nothing to be done but bite the bullet and change existing tests along with your application; and those are the occasions when the majority of the set-up code you quote would get into your test suite.

7
  • 2
    I disagree strongly to the first part. Tests must be independent. That is a far higher requirement in unit tests than in code, since independentness improves the maintainability of unit tests, while the lack of re-use harms production code.
    – Telastyn
    Nov 24, 2014 at 18:12
  • 1
    @Telastyn Tests can still be independent while sharing setup code. You just need to make sure you use a fresh fixture, which means either calling a shared setup method or using implicit setup (if your test framework supports it). Nov 24, 2014 at 21:26
  • 1
    @BenjaminHodgson - I don't see how a shared setup method can be changed for one test and not break another.
    – Telastyn
    Nov 24, 2014 at 21:36
  • 1
    @Telastyn But that applies to reused code in general - once a class has more than one client it's harder to change. Are you arguing for copy+paste duplication of fixture setup code in all unit tests? Nov 24, 2014 at 22:05
  • 3
    @Telastyn: if making tests independ from each other violates the DRY principle, you will unevitably run into problems when you try to improve the design of your code, but have to change 30 test methods with "similar setup" instead of one reused setup method. That's actually the top argument I hear often against TDD - too much effort for changing tests during refactoring - but it is almost always the problem the tests are not DRY enough.
    – Doc Brown
    Nov 25, 2014 at 6:45
2

It's like I need to know the innards of the Subject Under Test before even implementing it in order to Mock the dependencies and isolate the class, creating some kind of write test - write code - modify test if necessary cycle.

Yes, to some extent you do. So I don't think you're misunderstanding how TDD works.

The problem is that - as others have mentioned - it feels very odd at first, almost wrong to do it this way. In my opinion that's actually showcasing the what I feel is the biggest benefit of TDD: you have to properly understand the requirement before you write code.

As programmers, we like to write code. So what feels "right" and "natural" to us it to skim requirements and get stuck in as quickly as possible. The design problems then gradually become apparent as you build up and test the codebase. So you refactor and fix them and things gradually improve and move toward your goal.

Although fun, this isn't a particularly efficient way to do things. Far better to have a proper grasp of what a software module should be doing first, get your tests down and then write the code. It's less refactoring, less test maintenance and it forces you into better architecture out of the block.

I don't do a whole lot of TDD, and I think the "100% code coverage" mantra to be pointless. Especially in cases like yours. But adopting TDD still has a lot of value because it's such a boon in making sure things are well designed and well maintained across your code.

So in summary, the fact you're finding this bizarre is probably a good sign that you're on the right path.

0

Mocking data is just the practice of using dummy data.. the Moq frameworks make creating dummy data "easier".

ARRANGE | ACT | ASSERT

TDD generally is about creating your tests and then validating those tests "pass". Initially, the first test will fail since the code to validate that test has not been created yet.. I believe this is actually a certain type of testing; "red/green" testing, which i'm sure is the source of "Test Driven" methods today.

Generally, the tests validate the little nuggets of logic that make the bigger picture code work. You could start at the smallest level of function, and then work your way up to the more complicated functions.

yes, sometimes the setup or "mocking" will be somewhat intense, which is why using a moq framework is a good idea, however, if you focused on the core business logic, then you're tests will result in beneficial assurance that it works as expected and intended.

Personally, I don't test my controllers because everything that the controller is using has been tested to work, and generally, we don't need to test the framework.

Your Answer

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

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