SitePoint Sponsor

User Tag List

Results 1 to 11 of 11
  1. #1
    SitePoint Evangelist
    Join Date
    Apr 2008
    Location
    Dublin, Ireland
    Posts
    461
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    TDD - a question to any proponents.

    I've been trying to get into TDD but am hitting some stumbling blocks. I suppose a simplified example would be the best way to explain. My first assumption is that I start from the top. I want a web page that displays a list of categories say. So I write a test for a CategoryController whose Index action should return this list:-

    Code:
    [TEST]
    public void IndexActionShouldReturnListOfCategories()    {
        var catController = new CategoryController();
        var result = catController.Index() as ViewResult;
        Assert.IsInstanceOf(typeof(List<Category>), result.ViewDate.Model);
    }
    Now in true TDD fashion we get errors. No CategoryController exists so we create it:-

    Code:
        public class CategoryController : Controller {
            public ActionResult Index() {
                return View();
            }
        }
    Running our test will result in a fail because the view will not have a list of categories so refactor:-

    Code:
        public class CategoryController : Controller {
            public ActionResult Index() {
                var catList = new List<Category>();
                return View(catList);
            }
        }
    Bingo our test will pass. Now my problem here is how does TDD drive us beyond this point? Our tests have passed and it fulfills out requirements but of course we know that a fully working app would need say an ICategoryService to be passed into the Controller and the controller would use this to retrieve the categories. Now I can and will skip TDD and write in this service but I'm wracking my brains at how TDD can force us into this on it's own which is preferable I think because if TDD drives us our code will probably be optimal.

  2. #2
    Resident OCD goofball! bronze trophy Serenarules's Avatar
    Join Date
    Dec 2002
    Posts
    1,911
    Mentioned
    26 Post(s)
    Tagged
    0 Thread(s)
    Typically, we design from the core up. You're current method of going top to bottom leaves holes (as you noticed).

    You're primary concern here is Category. This is a domain entity and should be fully developed first, using TDD in the same way. As it is, we have no clue what the category properties are, or what a category can do.

    Next, test and design your persistence interfaces. Your tests should use mocks based on these interfaces, and may simply return an in memory db of categories. These interfaces should be used in the constructors of your controllers and / or services. This allows us to not care about implementation at this point.

    Next you can code your controller tests, which might now look like:

    [TEST]
    public void IndexActionShouldReturnListOfCategories() {
    var catRepository = new MockCategoryRepository();
    var catController = new CategoryController(catRepository);
    var result = catController.Index() as ViewResult;
    Assert.IsInstanceOf(typeof(List<Category>), result.ViewDate.Model);
    }

    So, to answer your question:

    It forces us only when we adhere to all the practices. Your controller should not have a parameterless ctor, like in your original test code. You should be using dependancy injection. Once you do this, you'll see that you NEED the repository object in order to continue, and therefore go test and develop one. In doing this, your also learn you need to know more about the category entity, and so you are off to test and develop that.

    TLDR : Go bottom up.

  3. #3
    ALT.NET - because we need it silver trophybronze trophy dhtmlgod's Avatar
    Join Date
    Jul 2001
    Location
    Scotland
    Posts
    4,836
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Look at your test and think about what your actually testing and what does it tell you about your app? To me, your simply testing the Mvc framework, your putting something in the ViewData and testing it was there. Trust me, there's already a test for that.

    Think about what your actually trying to do. What is the point of this action? What is the list and how should it be retrieved. What assertion will allow you feel confident that the test and production code is actually doing what it's supposed to do.

    I would write a test like this:
    Code Csharp:
    [TestFixture]
        public class when_viewing_the_home_action : ContextSpecification
        {
            private CategoryController controller;
            private Mock<IRepository> repository;
            private List<Category> expectedList;
            private ViewResult result;
     
            protected override void SharedContext()
            {
                repository = CreateDependency<IRepository>();
                controller = new CategoryController(repository);
            }
     
            protected override void Context()
            {
                expectedList = new List<Category> {new Category()};
                repository.Setup(x => x.FindAll<Category>()).Returns(expectedList);
            }
     
            protected override void Because()
            {
                result = controller.Index();
            }
     
            [Test]
            public void should_retrieve_all_categories()
            {
                result.ViewData.Model.ShouldEqual(expectedList);
            }
        }

  4. #4
    SitePoint Evangelist
    Join Date
    Apr 2008
    Location
    Dublin, Ireland
    Posts
    461
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    I'm not sure I was 100% clear here. I'm aware of entities, the repository pattern and so on. I'm simply trying to approach something from a TDD point of view to see how it can shape your code. When I said simple example I meant simple example. I could have used an unit test for a service instead of a controller. I'm not talking about the right or wrong approach. Surely pure TDD will send us down the right path? Or it should.

    I would have thought the re-factoring phase of TDD would be where your knowledge of various patterns would lead you to replace your bad code with better code and so on.

    I think what I'm really missing is acceptance/integration tests that in turn lead to individual unit tests that allow the integration test to pass. Collectively they contribute to drive your code perhaps.

    My example was to show that I can get a test to pass without it suggesting it's not enough. So in that sense my spec or acceptance testing is weak.

  5. #5
    Resident OCD goofball! bronze trophy Serenarules's Avatar
    Join Date
    Dec 2002
    Posts
    1,911
    Mentioned
    26 Post(s)
    Tagged
    0 Thread(s)
    "Surely pure TDD will send us down the right path?"

    Not per se. A lot of people have trouble learning the right way to do it. Just like a lot of people have trouble learning how to do MVC properly. I know I did. I speak from experience.

    There's more to TDD than just writing a test. There's a thought process involved. As D said above, think about your test subject and ask yourself all those annoying questions you used to ask back when you wrote specifications on paper.

    In a way, that's all TDD is: a coupling of the old paper spec with proving code.

    When I write a test, I sit down and stub all the tests, giving them wordy, descriptive names (like D's test fixture above), much in the same way as I would write out a bulleted list of conditions an object must meet in a paper spec. Then I begin writing the test and / or fixtures.

  6. #6
    SitePoint Evangelist
    Join Date
    Apr 2008
    Location
    Dublin, Ireland
    Posts
    461
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    But again thats not what I mean. I don't agree that we should all be sitting down and listing out the current patterns and practices de jour and deciding to use them before we start testing. Lets see we'll have a service, view models and so on.

    What I'm saying is there are a lot of good patterns and practices out there and some will suit project A perfectly but not project B. This is where I see TDD coming in to play. It will lead us in the right direction as to what patterns and practices will work best in each particular situation and I'm trying to fit this along side the good stuff out there.

    Unit testing is one part of the picture whereas TDD is a more all-encompassing methodology which will help you to discover where your code can be optimised and what patterns are best suited.

    Considering all we ever see in tutorials are unit tests I suspect most .net developers think that TDD = Unit Testing so maybe I'm looking for some good examples in .net that include more than that so I can see how the whole testing process drove the code or at least guided it.

  7. #7
    ALT.NET - because we need it silver trophybronze trophy dhtmlgod's Avatar
    Join Date
    Jul 2001
    Location
    Scotland
    Posts
    4,836
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    It's very rare I can sit down and decide ahead of time what patten to use. Generally my tests and specification's lead me spot a pattern is emerging and refactor to it. This is easily done because of the existing test suite.

    To return to your original post:

    Now I can and will skip TDD and write in this service but I'm wracking my brains at how TDD can force us into this on it's own which is preferable I think because if TDD drives us our code will probably be optimal.
    It's all about discipline. Starting with TDD can be hard, it took me a good while to really get to grips with it.

    Ok, so let's say you have your controller depend on the ICategoryRepository contract, FindAll. You know your app won't work because there is no class that implements it, so you need to write it. The test case for this is even because you already know the expected behaviour, it needs to return all the categories. Put together the quickest, dirtiest thing you can to make it pass, and refactor. Remember TDD is a 3 step process, with small feedback loops, Red-Green-Refactor. Never forget the refactor part, this is a core principle.

    If your not 100% sure whats next, write a test for the closet you can think of. The point is to define what the behaviour should be right now as you understand it. It might change it 10mins, but you will know your original assumption was incorrect, or needed tweaked. Driving it from your test means you always have that safety net.

    Just to repeat: TDD is hard to get your head around. But it's more than worth it, once its clicks, your code will never be the same again. And it will click, it just takes practice and lots of trial and error. I would recommend having a look at some OSS projects and their code/tests. Try applying the styles to your code and see how it goes. Your going to get it wrong before you get it right.

  8. #8
    SitePoint Author silver trophybronze trophy
    wwb_99's Avatar
    Join Date
    May 2003
    Location
    Washington, DC
    Posts
    10,651
    Mentioned
    4 Post(s)
    Tagged
    0 Thread(s)
    I can't claim to have done much pure TDD here -- I tend to start things out that way, but it tends to fall off a bit once we get past the initial experimental stages. And the bit about looking at some OSS projects tests rings so true it isn't even funny -- the good ones really show you the way. One project in particular I like is the MvcContrib, perhaps there are some others that should be named for reference.

  9. #9
    SitePoint Evangelist
    Join Date
    Apr 2008
    Location
    Dublin, Ireland
    Posts
    461
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Thanks for the replies guys. I think some learning by doing is called for.

  10. #10
    SitePoint Evangelist
    Join Date
    Apr 2008
    Location
    Dublin, Ireland
    Posts
    461
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Quote Originally Posted by Serenarules View Post
    Typically, we design from the core up. You're current method of going top to bottom leaves holes (as you noticed).
    Just wanted to follow up on this because there seems to be little discussion of TDD. Anyway I am firmly in the top-down camp now (or Outside-in in BDD langauge). I like the look of specflow with watin although I haven't used it in any real way as yet.

    If you leave out specflow though and consider it from an MVC view as an example you could literally create a view that inherits from a non-existent viewmodel and template out the information you want in that view. Then create the concrete viewmodel and start unit testing a non-existant controller that has to return that viewmodel and so on. Please note I am not advocating creating a view as a virtual unit test just making a point by example though in some ways it appeals to me in the abscence of specflow or something similar.

    The idea being you are starting with an outline of what the end user requires/expects not what you have decided in all your wisdom will eventually deliver what they need. So are people still jumping in and unit testing their domain objects from the beginning or have we any BDD "Outside-In" converts yet?

  11. #11
    Resident OCD goofball! bronze trophy Serenarules's Avatar
    Join Date
    Dec 2002
    Posts
    1,911
    Mentioned
    26 Post(s)
    Tagged
    0 Thread(s)
    Brian, your concern seems to be one of workflow, not really one of TDD.

    I used to be a top-down man myself, until I tried TDD. What happened was, I developed a controller, then a repository, and then the supporting entity, only to find that the entity didn't correctly match what was defined in the UL.

    Whoops...

    When I redid it all, starting with the entity first, things fell into place.

    I guess, if you're comfortable with top-down, then by all means do it. But to me, it's like asking Ford to design a vehicle frame before considering engine design. If the engine doesn't fit...whoops again.


Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •