Take the 2-minute tour ×
Programmers Stack Exchange is a question and answer site for professional programmers interested in conceptual questions about software development. It's 100% free, no registration required.

TDD proponents claim that it results in better design and decoupled objects. I can understand that writing tests first enforces the use of things like dependency injection, resulting in loosely coupled objects. However, TDD is based on unit tests - which test individual methods and not the integration between objects. And yet, TDD expects design to evolve from the tests themselves.

So how can TDD possibly result in a better design at the integration (i.e. inter-object) level when the granularity it addresses is finer than that (individual methods)?

share|improve this question

migrated from stackoverflow.com Oct 21 '13 at 19:37

This question came from our site for professional and enthusiast programmers.

1  
Possible duplicate of stackoverflow.com/questions/18988040/… –  Raedwald Oct 3 '13 at 18:52

6 Answers 6

It depends. In TDD it doesn't have to be that there is a one-to-one relationship between a test and a class. Refactoring can produce many classes that are essentially under one set of tests. In that sense, those class interactions are tested.

That being said, there are boundaries where you would mock out certain interactions, whether using a mocking framework, or just in terms of decoupling. Those boundaries are subject to different testing, that is beyond TDD. In terms of TDD, you make the surface and the complexity of creating the coupling as small as reasonably possible and then the actual coupling doesn't get tested by TDD.

The coupling should generally be a line or two of code. If it is too complicated, you may need to test the coupling mechanism independent of the whole system, but that gets back to one of those boundaries.

Then you are left with an issue which is beyond TDD - I wrote a class or group of classes to do A, with the idea that class or group of classes B would interact with it. But did I mess that up? Is there a subtle difference in what I was thinking when I wrote one vs what I was thinking when I wrote the other? And of course in a multi-person project each side might not have been written by the same person.

That is integration testing, acceptance testing, QA, etc. It is test after, not test driven, even if your acceptance tests are written before, it isn't really the same.

share|improve this answer
    
Re your first paragraph, if you are testing class interactions, then they aren't unit tests in the strictest sense (they're integration tests). Re the rest, if I understand you correctly, class relationships are beyond the scope of TDD. Then how is the claim that TDD results in better design justified? –  Gigi Oct 2 '13 at 18:12
    
On point one, TDD isn't about achieving unit tests in the strictest sense. It is simply not one of its goals. On the rest, the TDD drives these class relationships, as does the overall architecture, by having things being in testable units one of the important drivers of the design. –  Yishai Oct 2 '13 at 21:30
    
As I commented on other answers, TDD seems to usually be defined in terms of red-green-factor and the three rules, which speak only of unit tests. I can accept that some people have a broader definition, but if these criteria are the 'real' TDD, then I'm interested in knowing if it can really be applied to a larger design as so many people proclaim so religiously. –  Gigi Oct 3 '13 at 15:28
    
@Gigi, you are bringing your own definition of unit tests and reading it into what they write. They are simply not limiting unit tests to test which test one class without involving another. And the main advocates cannot really be described as purists. Anyway, the purist definition is contrived. Can you not use ArrayList in your class because then you are testing an integration between it and your class? Same thing if you write the class instead of taking one from the JDK. –  Yishai Oct 3 '13 at 22:29

I disagree with your assumption that unit tests have to be method-scope. Unit tests don't have to be limited to testing individual methods, they can test interactions between objects too.

The difference between a unit test and an integration test is that the integration test requires some kind of external resource (database, queue, file system, etc.) to be present. The unit test would use mocks to stand in for things at the boundaries of the application.

There is an idea that unit tests should be small. The wikipedia article on Unit testing says:

In computer programming, unit testing is a method by which individual units of source code, sets of one or more computer program modules together with associated control data, usage procedures, and operating procedures are tested to determine if they are fit for use.1 Intuitively, one can view a unit as the smallest testable part of an application. In procedural programming, a unit could be an entire module, but is more commonly an individual function or procedure. In object-oriented programming, a unit is often an entire interface, such as a class, but could be an individual method. [2] Unit tests are created by programmers or occasionally by white box testers during the development process.

So the idea of what constitutes a unit seems very flexible to me.

There's no reason a unit test can't instantiate different objects and test the results of using them together, that can be a valuable way of testing that the code is internally self-consistent.

share|improve this answer
    
That's not what I'm asking (actually I asked that in another question). My point is: does TDD really result in better design beyond the unit level? How is that possible, given that TDD advocates unit tests (although, as you say, it is entirely possible to do integration tests as well)? –  Gigi Oct 2 '13 at 18:05
    
@Gigi: I was addressing your assumption that TDD can only test at the method level, I don't think that's necessarily true. Unit tests can test internal consistency of the application code, they only stop short of verifying interaction with external systems. That's what I was trying to get across by distinguishing between unit- and integration-tests. If unit tests were necessarily method-scope then I would agree their helpfulness would be extremely limited. –  Nathan Hughes Oct 2 '13 at 18:10
    
I understand, and your point is perfectly valid. However, the purist approach is that TDD is unit tests only. It is with the purist approach that I am taking issue. Nonetheless, your input is useful and I'd like to invite you to post an answer to my other question on this exact topic: stackoverflow.com/questions/19142855/… . –  Gigi Oct 2 '13 at 18:16
    
@Gigi "the purist approach is that TDD is unit tests only" - which purist stated this? As Nathan Hughes says TDD can be used equally well at an interaction or a unit level, where the integration tests will drive the creation of unit tests to satisfy the integration test. –  Fresh Oct 2 '13 at 21:30
    
How about Uncle Bob? butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd The three rules are specific about unit tests. –  Gigi Oct 3 '13 at 15:21

To answer your question, I would say, what you state is right, partly.

TDD makes extensive use of unit tests, but also acceptance test. If you look at the full TDD test cycle described in Growing Object-Oriented Software, Guided by Tests, it is:

  1. Write a failing acceptance test
  2. Write a failing unit test
  3. Make the failing test pass
  4. Refactor
  5. Repeat steps 2-4 until the failing acceptance test passes

By definition acceptance tests are end-to-end tests that start from the user input level through all your application stack (including domain objects, external services and databases).

Because of this you will not only test your components and define interaction there, but also base them on overarching acceptance tests that proof your components work together.

This will provide you with enough certainty so you are able to impose structural and architectural improvements on your code whenever required.

If you want to see this in action check this TDD session with Uncle Bob.

share|improve this answer
    
Are the acceptance tests automated like unit tests? –  Gigi Oct 3 '13 at 15:30
    
sure, you can use any test framework, but also specialized frameworks like Fit, Fitnesse or Specflow (the latter is more specialied towards BDD) –  Jesko R. Oct 3 '13 at 17:12
    
Right, so you're saying that for TDD we should have automated tests through the UI (that way it's from user input level)? –  Gigi Oct 3 '13 at 17:18
1  
Ideally, yes. However,in practice, you will have to use a UI pattern like MVVM and MVC/MVP to make testing feasible. Full UI testing will be much more a topic for system and regression testing. But if you abstract your UI logic from presentation correctly it's not very risky that way. –  Jesko R. Oct 3 '13 at 17:26
    
Good point about MVVM - I'll give it some thought and see if I can understand how it scales. –  Gigi Oct 3 '13 at 17:48

I think you want to look at integration tests, where you might mock out the classes which are the individual components and their expected results and perform actions on a collection of those.

You should set the output of test or mocked objects to known/expected values, you are then purely testing methods which interact on those specific objects.

Take an example where you have two classes, Person and Car for example. The individual class based unit tests would test say functions on each of those classes Person.SetName() and Car.SetMake() for example.

Your integration test might be the testing of a CarParkService where you'd pass in a Car and Person in a known state (potentially mocked) and call the method ParkACar(Person p, Car c). With this way of working you're purely testing the integration of the person and the car within the context of a service.

You can test this a lot further, but as far as I view it, in a simple sense integration tests will test the combination of classes/methods as a group (such as a service).

share|improve this answer
    
Like the other answers, what you say is valid. However I am questioning whether TDD can possibly result in a better overall design when it is concerned with the unit level and not integration per se. –  Gigi Oct 2 '13 at 18:47
    
I suppose it would depend on what you mean by TDD. TDD is not limited to unit tests in my book, I see it as a wider discipline regarding writing the expectations of your functionality before you develop it. Whilst in its simplest/purest form it may be unit tests for single classes, I believe it does/should extend to integration tests between groups of classes and functionality. Testing the way individual classes fit together helps route out design issues by noticing when dependencies are difficult to provide or when the inputs and outputs of methods are not easy to provide or validate. –  dougajmcdonald Oct 3 '13 at 7:01
    
When researching TDD, the most common two things I see are the red-green-refactor cycle and the three rules (butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd). They seem pretty focused on unit testing and nothing else, although I understand people may adopt different practices as they deem practical. –  Gigi Oct 3 '13 at 15:25

Unfortunately the definition of unit test is very fuzzy. One of the most cited definitions is done through the properties the tests should have, which are

  • runs fast
  • is isolated (no interactions between tests, i.e. run them in any order and always get the same result)
  • requires no external configuration
  • provides a consistent pass/fail result It's from Roy Osherove's book, the art of unit testing.

If I were to add a definition, I'd say a unit test asserts against the result of a logical path through a method where you have control over the result of all dependencies involved (fwiw, that's just my definition).

Imagine you have to create a method that checks if a string is valid, and if so does something, if not does something different. This is how we could write that method if we want it to be unit testable:

class SomeClass
{
    IStringAnalyzer stringAnalizer;
    ILogger logger;

    public SomeClass(IStringAnalyzer stringAnalyzer, ILogger logger)
    {    
        this.logger = logger;
        this.stringAnalyzer = stringAnalyzer;
    }


public void SomeMethod(string someParameter)
    {

    if (stringAnalyzer.IsValid(someParameter))
    {
        //do something with someParameter
    }else
    {
        logger.Log("Invalid string");
    }
}

stringAnalyser and logger are dependencies because we want to be able to control what they return (through a stub) and how they are used (through a mock).

This is so you can write a test like this (that has all the nice characteristics that Roy describes in his book):

[Test]
public void SomeMethod_InvalidParameter_CallsLogger
{
    Rhino.Mocks.MockRepository mockRepository = new Rhino.Mocks.MockRepository();
    IStringAnalyzer s = mockRepository.Stub<IStringRepository>();
    s.Stub(s => s.IsValid("something, doesnt matter").IgnoreParameters().Return(false);
    ILogger l = mockRepository.DynamicMock<ILogger>();
    SomeClass someClass = new SomeClass(s, l);
    mockRepository.ReplayAll();

    someClass.SomeMethod("What you put here doesnt really matter because the stub will always return false");

    l.AssertWasCalled(l => l.Log("Invalid string"));
}

The benefits are that the "several concerns" are all decoupled and they are very easy to spot when you are doing the test first. You'd see that you would need to leave the logic of StringAnalizer out of the class you are testing because the need for controlling if a string is valid or not would be evident.

That test makes use of an isolation framework (rhino mocks) and I explain what that is here: http://blinkingcaret.wordpress.com/2012/11/20/interaction-testing-fakes-mocks-and-stubs/

Regarding the discussion of TDD really produces better designs all I can say is this: http://blinkingcaret.wordpress.com/2012/10/02/tdd-bdd-add-every-other-method-that-promises-software-quality-here-and-the-tea-tasting-lady/ And this: http://vimeo.com/9270320

share|improve this answer

So how can TDD possibly result in a better design at the integration (i.e. inter-object) level when the granularity it addresses is finer than that (individual methods) ?

Because granularity evolves as a result of TDD :)

The fact that the entry point in a test (Act part in Arrange Act Assert) is a single method doesn't mean all resulting production code will be contained in that method at the end of the TDD cycle.

A (the most ?) important step in TDD is refactoring. Through refactoring, code can be moved to another method or another class.

share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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