3

TDD is supposed to have 100% code coverage. Does this mean one is supposed to write tests for property getter and setters, and other methods that contain no real logic, such as dealing with external API functionality?

Example 1:

Below is one example method (which happens to also be the example in this other SO question which deals with how best to test it, if we are going to test it). This method doesn't do much. It's a facade of the System.ServiceProcess.ServiceController functionality of stopping a service. Currently this code was not being written using TDD, but if it was, would it be something that one should test? There is very little logic here. The test in and of itself wouldn't be that helpful.

FYI: If you'd like to answer on how best to test it (IoC & Adapter Pattern vs. Detouring) please see this other SO question.

Public Function StopService(ByVal serviceName As String, ByVal timeoutMilliseconds As Double) As Boolean Implements IWindowsServicesService.StopService

    Try
        Dim service As New ServiceController(serviceName)
        Dim timeout As TimeSpan = TimeSpan.FromMilliseconds(timeoutMilliseconds)

        service.[Stop]()

        If timeoutMilliseconds <= 0 Then
            service.WaitForStatus(ServiceControllerStatus.Stopped)
        Else
            service.WaitForStatus(ServiceControllerStatus.Stopped, timeout)
        End If

        Return service.Status = ServiceControllerStatus.Stopped

    Catch ex As Win32Exception
        Return False
    Catch ex As TimeoutException
        Return False
    End Try

End Function

Example 2:

In case one argues that the code still has some logic, and therefore it needs to be tested when doing TDD, then what about the following code that has no logic:

Public Function GetProcess(ByVal serviceName As String) As Process
    Dim managementObject As New ManagementObject(String.Format("Win32_service.Name='{0}'", serviceName))
    Dim processID As Integer = CType(managementObject.GetPropertyValue("ProcessID"), Integer)
    Dim process As Process = process.GetProcessById(processID)
    Return process
End Function
Community
  • 1
  • 1
Matt
  • 14,353
  • 5
  • 53
  • 65
  • 1
    If you're writing the code, then wondering how to test it, you're missing the point of TDD. – Anon. Feb 10 '11 at 03:28
  • @Anon I wasn't trying to TDD by writing the code first. I was wondering if I did, would the TDD approach really unit test every method, property, etc., regardless of if there is anything there to really test, from a logic point of view. As regards how to test, well I would either use IoC and the Adapter pattern, or use Microsoft Moles to isolate the test from the .net framework dependecies. – Matt Feb 10 '11 at 03:34
  • Looking at your second sample, there *is* logic there - getting the process corresponding to a service is not trivial the way that, say, `get { return _value; }` is. I'm wondering why you suggest that that code should not be tested at all. – Anon. Feb 10 '11 at 03:39

3 Answers3

11

TDD is not supposed to have 100% code coverage. TDD tends to have very high code coverage, as compared to other methodologies. If you don't write a line of code without a failing test, if you follow that strictly, then yes, you'll get to 100% coverage. But most of us don't follow that strictly, and perfect code coverage is not the objective of TDD. High code coverage is a nice side effect of Test-Driven Development, but it is not the point.

Most of us don't test-drive simple getters and setters specifically as part of the TDD process. But we're not creating that field which needs the getter and setter until we have a need for it - where "need" is defined as a failing test. So the getter & setter will be tested as a matter of course, because they're created as needed by methods we are test driving.

Both of your examples have enough logic in them to be worthy of tests; I would test-drive both of them.

Carl Manaster
  • 39,912
  • 17
  • 102
  • 155
  • 1
    +1 Thanks for this answer. This helps. Though, I obviously am struggling a bit with knowing what to test in a method like `GetProcess`. It's just a series of API-like calls. What exactly would I test for? The code won't break unless the service doesn't exist. So I could test for an exception like ServiceNotFound, but what else? That it returns a process object? It doesn't seem like much is happening here. Please understand that I am fairly new to using test. If you care to discuss that method, we can make it easier. Replace the parameter with just a string for the name (I updated the example) – Matt Feb 10 '11 at 04:25
  • 2
    @Matt - the point is if tommorrow someone changed the above code such that it no longer worked, would you be able to identify the cause without major debugging hours? To unit test the method, I'd create a couple of test-cases. Verify that method returns the right process object (inspect attributes) when a valid service name is specified. Verify that an exception is thrown when the service does not exist. -- To say that GetProcess has no logic is incorrect, it's just that the logic is wrapped up within the methods called. – Gishu Feb 10 '11 at 06:21
  • Great answer Carl. I would like to add, that once you get used to TDD, testability will drive you to make code in smaller pieces - when limiting the responsibility of any given method (or class), it becomes so much easier to test. After a while most of your methods, will rarely exceed the examples given in the question, and classes will seldom be much more that a couple of screens high. – Morten Feb 18 '11 at 01:31
5

In the interest of full disclosure: I'm no expert in TDD (I don't actually use it and never have), but I think I can argue from my ignorance for the TDD advocates in this case.

Imagine a situation where you have a simple "zero logic" property/function like example two. Suppose you implemented your own version of GetProcessById which functions very slightly differently, but is interchangable with the Process object. You decide not to write a test for this function, and say "Ahh, I'm just delegating it to the well-tested library, I can't possibly screw it up." There's your first mistake. "I can't possibly screw it up" is the single worst lie that programmers ever regularly tell themselves.

Suppose six months from now you realize you need to extend Process with some extra stuff. You delegate all the right methods and override GetProcessById. You now officially have to test this "zero logic" method. And that's the trouble. Polymorphism and many other features of programming languages (even ones that aren't strictly Object Oriented) lead to code which doesn't do exactly what you imagine it to do.

So, that being said, if you're following a strict TDD methodology, and you're striving for 100% test coverage, you'll want to test "zero-logic" methods, just like those two.

The only exception I can imagine is specific to .NET and that's automatically implemented properties, where there's no point in testing that the Getter and Setter work correctly, because even if they didn't there'd be nothing you could do about it. Test the object being wrapped in the property, not the property itself.

TDD folks, does that summarize it pretty well?

Chris Pfohl
  • 18,220
  • 9
  • 68
  • 111
  • +1 from me. I think you nailed it... (I'm not a TDD expert, but I still can't find a flaw in your argument)... – ircmaxell Feb 10 '11 at 03:44
  • The TDD rule of thumb is "test everything which could possibly break". So I may not write a test now, but I will write the test six months later when I need it. – Péter Török Feb 10 '11 at 20:24
  • Right, but for someone who's still trying to figure out what might break a better rule might be "if you've written code, test it!" – Chris Pfohl Feb 11 '11 at 15:32
1

Full disclosure: I don't use formal TDD and never have

Your code examples encapsulate a third-party complex API to get something done. Where will the bugs come from?

Most bugs will be caused by unexpected, possibly undocumented, behaviour from the API. It can be really hard to get those things to act right in every situation (Windows version / regional setting / network setup etc.) You can actually become a world-famous blogger just by documenting mistakes and misunderstandings people have made with the Win32 API.

  • The most important tests are tests against the real API. You need some setup and teardown code to create processes or services.
  • Mocking/IoC aren't very valuable. You're just proving that you are making particular API calls in a particular order. You need to prove that your code is achieving what you want, in all the situations it will encounter in the real world.
  • I really think you can't do test-first development against mocks here. Imagine you find a bug in the third-party API (it happens). You'll need to work round the bug. You might even need to totally change your approach, maybe PInvoke to the Win32 API or some COM object. In that situation, mocked tests would just be a waste of time.
    1. First you'd have to code up a workaround that actually works against the real API.
    2. Then you'd have to edit the mocked tests to match the workaround.
    3. You couldn't edit the mocked tests first, because you have to try the workaround out to know whether it actually works.

My world is vertical-market desktop software. We have a library of routines like yours and we have realistic tests for them. Occasionally we find a bug on a particular Windows version / regional setting / whatever. Then we extend the tests to reproduce the bug and verify the fix, and make sure to rerun them on our set of test environments. Note to the reader: if your code only has to work on one server, maybe your world is different and you won't need to do this.

MarkJ
  • 30,070
  • 5
  • 68
  • 111
  • Thanks for coming over here to elaborate! My world is also vertical-market desktop software. I agree that this code is a perfect candidate for integration & QA testing! But I'm not sure that TDD (TAD even) isn't very useful for shrink-wrapped software. Wouldn't it still allow one to have a more maintainable design? Ultimately the purpose of TDD (or TAD with IoC) is to [reinforce the developer's ability to think and reflect on the design of code which ultimately improves it's quality/maintainability. ](http://michaelfeathers.typepad.com/michael_feathers_blog/2008/06/the-flawed-theo.html) – Matt Feb 10 '11 at 13:37
  • ...(continued) Catching defects is not apparently what makes TAD and unit testing with IoC in general so useful. Rather the usefulness comes in how they help shape a more maintainable design, one that takes advantages of patterns, loose coupling, and high cohesion. – Matt Feb 10 '11 at 13:44
  • @Matt I'm sure TDD could be great for shrink-wrapped software in general. I should clarify that my answer is quite specific to those sample routines in your question. So, I guess I'm saying, for routines like the ones in your question: by all means write the tests first if it will help you develop your facade/wrapper abstractions - but do use real tests, not mocking. For the rest of your code, use mock or IoC as it seems appropriate to you. I can imagine you might mock/IoC these sample routines when you are testing their clients. – MarkJ Feb 10 '11 at 17:02