2
\$\begingroup\$

This is my very-first-ever run at TDD. How did I do?

In keeping with Uncle Bob's Three Rules, I wrote every test first, starting in order from the top down as you see here (below). Here's the sequence I used:

  1. For each new method in Production I entered the method-name-to-be in Test.
  2. I received a compile-time error.
  3. I used Visual Studio's error correction feature to generate the method stubs.
  4. I built the solution and ran all tests, resulting in a failure on the most recent test.
  5. I wrote Production code to cause the failed test to pass.
  6. I built the solution and ran all tests, verifying that they all passed.

To tell you the truth, this all seems pretty redundant. As you can see, each of my tests contains code copied from previous tests--I had to do that so the test would run. I tried making functions out of them, so I could chain them together, but that caused Visual Studio to not see the tests.

Certainly there must be a better way. I'd love to know what it is.

Production:

Public Module Main
  Sub Main()
  End Sub

  Function GetSessionType(RemoteHostName As String) As Type
    Return Type.GetTypeFromProgID("Microsoft.Update.Session", RemoteHostName)
  End Function

  Function GetSession(Type As Type) As UpdateSession
    Return Activator.CreateInstance(Type)
  End Function

  Function GetSearcher(Session As UpdateSession) As IUpdateSearcher
    Return Session.CreateUpdateSearcher
  End Function

  Function GetHistoryCount(Searcher As IUpdateSearcher) As Integer
    Return Searcher.GetTotalHistoryCount
  End Function

  Function GetHistory(Searcher As IUpdateSearcher, Count As Integer) As IUpdateHistoryEntryCollection
    Return Searcher.QueryHistory(0, Count)
  End Function

  Function EnumerateHistory(History As IUpdateHistoryEntryCollection, Count As Integer) As Boolean
    Dim oEntry As IUpdateHistoryEntry

    EnumerateHistory = True

    For iIndex = 0 To Count - 1
      Try
        oEntry = History(iIndex)
        Console.WriteLine("Title: {0}{2}Date: {1}", oEntry.Title, oEntry.Date, vbTab)

      Catch ex As Runtime.InteropServices.COMException
        ' For some reason on MyComputer the GetTotalHistoryCount() method  '
        ' call returns a count higher than the existing update count. This '
        ' catch bypasses the resulting IndexOutOfRange COM errors.         '

      Catch ex As Exception
        EnumerateHistory = False

      End Try

    Next
  End Function
End Module

Test:

Imports System.Text
Imports Microsoft.VisualStudio.TestTools.UnitTesting

<TestClass()>
Public Class UpdateHistoryReaderTests
  Private Const REMOTE_HOST_NAME As String = "MyComputer"

  <TestMethod()>
  Public Sub GetSessionTypeTest()
    Dim oType As Type

    oType = UpdateHistoryReader.Main.GetSessionType(REMOTE_HOST_NAME)

    Assert.IsNotNull(oType)
  End Sub

  <TestMethod()>
  Public Sub GetSessionTest()
    Dim oSession As UpdateSession
    Dim oType As Type

    oType = UpdateHistoryReader.Main.GetSessionType(REMOTE_HOST_NAME)
    oSession = UpdateHistoryReader.Main.GetSession(oType)

    Assert.IsNotNull(oSession)
  End Sub

  <TestMethod()>
  Public Sub GetSearcherTest()
    Dim oSearcher As IUpdateSearcher
    Dim oSession As UpdateSession
    Dim oType As Type

    oType = UpdateHistoryReader.Main.GetSessionType(REMOTE_HOST_NAME)
    oSession = UpdateHistoryReader.Main.GetSession(oType)
    oSearcher = UpdateHistoryReader.Main.GetSearcher(oSession)

    Assert.IsNotNull(oSearcher)
  End Sub

  <TestMethod()>
  Public Sub GetHistoryCountTest()
    Dim oSearcher As IUpdateSearcher
    Dim oSession As UpdateSession
    Dim iCount As Integer
    Dim oType As Type

    oType = UpdateHistoryReader.Main.GetSessionType(REMOTE_HOST_NAME)
    oSession = UpdateHistoryReader.Main.GetSession(oType)
    oSearcher = UpdateHistoryReader.Main.GetSearcher(oSession)
    iCount = UpdateHistoryReader.Main.GetHistoryCount(oSearcher)

    Assert.IsFalse(iCount = 0)
  End Sub

  <TestMethod()>
  Public Sub GetHistoryTest()
    Dim oSearcher As IUpdateSearcher
    Dim oHistory As IUpdateHistoryEntryCollection
    Dim oSession As UpdateSession
    Dim iCount As Integer
    Dim oType As Type

    oType = UpdateHistoryReader.Main.GetSessionType(REMOTE_HOST_NAME)
    oSession = UpdateHistoryReader.Main.GetSession(oType)
    oSearcher = UpdateHistoryReader.Main.GetSearcher(oSession)
    iCount = UpdateHistoryReader.Main.GetHistoryCount(oSearcher)
    oHistory = UpdateHistoryReader.Main.GetHistory(oSearcher, iCount)

    Assert.IsNotNull(oHistory)
  End Sub

  <TestMethod()>
  Public Sub EnumerateHistoryTest()
    Dim oSearcher As IUpdateSearcher
    Dim oHistory As IUpdateHistoryEntryCollection
    Dim oSession As UpdateSession
    Dim lSuccess As Boolean
    Dim iCount As Integer
    Dim oType As Type

    oType = UpdateHistoryReader.Main.GetSessionType(REMOTE_HOST_NAME)
    oSession = UpdateHistoryReader.Main.GetSession(oType)
    oSearcher = UpdateHistoryReader.Main.GetSearcher(oSession)
    iCount = UpdateHistoryReader.Main.GetHistoryCount(oSearcher)
    oHistory = UpdateHistoryReader.Main.GetHistory(oSearcher, iCount)
    lSuccess = UpdateHistoryReader.Main.EnumerateHistory(oHistory, iCount)

    Assert.IsTrue(lSuccess)
  End Sub
End Class
\$\endgroup\$

closed as unclear what you're asking by Jamal May 12 '18 at 20:00

Please clarify your specific problem or add additional details to highlight exactly what you need. As it's currently written, it’s hard to tell exactly what you're asking. See the How to Ask page for help clarifying this question. If this question can be reworded to fit the rules in the help center, please edit the question.

  • 1
    \$\begingroup\$ Actually I'm not sure what your code should do at all. Just a general hint for starting with TDD: Start with a CodeKada to get a feeling for the approach and don't try it in first instance in your real code. Of course there is a little gap between the theory and the practical work and sometime you will create more than one test at once etc. \$\endgroup\$ – mheinzerling May 21 '14 at 6:41
  • \$\begingroup\$ As far as I can (and I care to) read vb.net, you seem to have violated rule no. 1. "You are not allowed to write any production code unless it is to make a failing unit test pass.", this includes writing the production code in your head. \$\endgroup\$ – abuzittin gillifirca May 21 '14 at 9:37
  • \$\begingroup\$ @mnhg - Pardon my delay; I was called away for a few days. The intent of the code is pretty simple--it connects to a remote workstation on the LAN and dumps a list of installed Windows Updates. I'll have to admit that you've lost me with the CodeKada reference--how does it relate to this? \$\endgroup\$ – InteXX May 24 '14 at 0:50
  • \$\begingroup\$ @abuzittingillifirca - Pardon my delay; I was called away for a few days. Aha! You caught me. Good catch. How'd you do that? :-) I already had the production code in hand; it was from an example I found on how to enumerate the list of installed Windows Updates. It was simple enough that I wanted to use it for my first TDD run. But thanks for the tip--I'll try to adhere to that in the future. \$\endgroup\$ – InteXX May 24 '14 at 0:55
  • 1
    \$\begingroup\$ @InteXX I just wanted to state, that you might need an "easier" scenario to learn and experiment with TDD before you use it in you real project. Sometime it is a kind of tricky to find the correct TDD approach if you don't have that much experience. \$\endgroup\$ – mheinzerling May 24 '14 at 7:26
6
\$\begingroup\$

Assert.IsNotNull(oSession) is in general a really weak test. Maybe you want to test anything in the session or the history etc.

Testing Assert.IsTrue(UpdateHistoryReader.Main.EnumerateHistory) is usually also not enough. What if your success return code is buggy. You need to test the operation itself and not only the return code.

\$\endgroup\$
  • 1
    \$\begingroup\$ +1 Of course according to Rule 3 OP is not allowed to write anything more than EnumerateHistory = True (or whatever is the equivalent of return true in VB) in the EnumerateHistory function. \$\endgroup\$ – abuzittin gillifirca May 21 '14 at 10:04
  • \$\begingroup\$ "Assert.IsNotNull() is in general a really weak test" OK, sounds good. Would the Assert.IsTrue() problem be solved by returning the enumerated list instead of a Boolean? \$\endgroup\$ – InteXX May 24 '14 at 1:05
  • \$\begingroup\$ @abuzittingillifirca - To tell you the truth, that Boolean return bothered me from the start. See my question to mnhg. \$\endgroup\$ – InteXX May 24 '14 at 1:06
  • \$\begingroup\$ @InteXX Actually you need to test the whole session content, so you need an assert for every property. If you might not care for the content right now, this brings me back to my initial statement, saying that this might be a bad scenario for TDD. \$\endgroup\$ – mheinzerling May 24 '14 at 7:29
  • \$\begingroup\$ "...this might be a bad scenario for TDD." Are you aware of any beginner's tutorial or sample? \$\endgroup\$ – InteXX May 24 '14 at 18:21
3
\$\begingroup\$

Although you cannot chain unit test methods, you can chain private helper methods, called by unit test methods. For example:

  Private Sub GetSessionType() As Type
    Return UpdateHistoryReader.Main.GetSessionType(REMOTE_HOST_NAME)
  End Sub

  <TestMethod()>
  Public Sub GetSessionTypeTest()
    Dim oType As Type

    oType = GetSessionType

    Assert.IsNotNull(oType)
  End Sub

  Private Sub GetSession() As UpdateSession
    Return UpdateHistoryReader.Main.GetSession(GetSessionType)
  End Sub

  <TestMethod()>
  Public Sub GetSessionTest()
    Dim oSession As UpdateSession

    oSession = GetSession

    Assert.IsNotNull(oSession)
  End Sub

And so on.

This program is not a great example of TDD in action, because you don't seem to have a lot of logic there. Your code is just a pipeline of calls, on a single execution path. Normally you would have many possible execution paths, where testing is much more important.

\$\endgroup\$
  • \$\begingroup\$ The testing of "possible execution paths" should be verified by code coverage tool, not by unit test. Unit test itself should cover possible inputs and outputs. \$\endgroup\$ – eriawan May 22 '14 at 2:32
  • \$\begingroup\$ Whew. I can see I have a lot of learning to do with this. \$\endgroup\$ – InteXX May 24 '14 at 1:09
  • \$\begingroup\$ @eriawan - I'm cornfused. Are you countering janos' statement or supporting it? \$\endgroup\$ – InteXX May 24 '14 at 1:10
  • 1
    \$\begingroup\$ When you say: "Your code is just a pipeline of calls, on a single execution path. Normally you would have many possible execution paths, where testing is much more important," is something like this paragraph what you're referring to: "Another thing to keep in mind with the autogeneration of unit tests is that unit tests do not map one to one with methods and properties. You don’t test methods one by one. You test logical pathways through the code." \$\endgroup\$ – InteXX May 24 '14 at 2:41

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