Click here to Skip to main content
15,883,776 members
Articles / Programming Languages / Visual Basic

Test Driven Development Example - CUSIP Validation

Rate me:
Please Sign up or sign in to vote.
4.20/5 (4 votes)
25 Feb 2014CPOL3 min read 16.5K   5   2
A worked example of test driven development using Microsoft.VisualStudio.TestTools.UnitTesting

Introduction

The pseudo algorithm for validating a CUSIP number is documented on Wikipedia and is a fairly straight forward piece of code.

What I am showing here is not how to do that part, but how you could use Microsoft.VisualStudio.TestTools.UnitTesting to code such a validator in a test-driven manner.

Create the Scaffold

The first step is to create an empty stub of a class that will do the actual logic of the CUSIP validation:

VB.NET
Public MustInherit Class SecurityIdentifier

    Private _securtityIdentifier As String

    Public ReadOnly Property Identifier As String
        Get
            Return _securtityIdentifier
        End Get
    End Property

    Public Sub New(ByVal securityIdentifier As String)
        _securtityIdentifier = securityIdentifier
    End Sub

End Class

Public Class CusipIdentifier
    Inherits SecurityIdentifier
    Implements ISecurityIdentifierValidation


    Public Function IsValid() As Boolean Implements ISecurityIdentifierValidation.IsValid
        Return False
    End Function

    Public Sub New(ByVal cusipSecurityIdentifier As String)
        MyBase.New(cusipSecurityIdentifier)
    End Sub
End Class

You can see at this stage that the validation does not work - it always returns false.
Now we put a suite of tests up that will test our CUSIP validator once it is written.

Create the Tests

Firstly, we create a new class and decorate it with the TestClass attribute. This attribute just tells the test runner that there are tests in this new class:

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

<TestClass()>
Public Class CusipUnitTest
End Class

Now we throw in some tests to check that the class constructor works for a series of different possible values of the cusipSecurityIdentifier value:-

VB.NET
<TestMethod()>
<TestCategory("CUSIP")>
Public Sub CusipEmptyCreateTestMethod()
    Dim cusipTest As New CusipIdentifier(String.Empty)
    Assert.IsNotNull(cusipTest)
End Sub

<TestMethod()>
<TestCategory("CUSIP")>
Public Sub CusipBlankCreateTestMethod()
    Dim cusipTest As New CusipIdentifier("")
    Assert.IsNotNull(cusipTest)
End Sub

<TestMethod()>
<TestCategory("CUSIP")>
Public Sub CusipNothingCreateTestMethod()
    Dim cusipTest As New CusipIdentifier(Nothing)
    Assert.IsNotNull(cusipTest)
End Sub

Every test ends in an Assert statement - if that statement comes out true, the test passes. If the assert comes out false, or any exception occurs, the test is considered a fail.

Positive Tests

Then we add some positive tests. These are tests that check the code works for some values for which we do expect it to work. In my case, I did this by taking a set of existing company's CUSIP identifiers which must - we assume - be valid.

VB.NET
'Agilent - 00846U101
<TestMethod()>
<TestCategory("CUSIP")>
Public Sub CusipAgilentValidTestMethod()
    Dim cusipTest As New CusipIdentifier("00846U101")
    Dim expected As Boolean = True
    Dim actual As Boolean = False

    actual = cusipTest.IsValid()

    Assert.AreEqual(expected, actual)

End Sub

The thing to note here is that we always initialize the "expected" value to be different to the "actual" variable value. This is so that a test can only pass if that variable value is changed by the test. For good coverage, a set of 30 or so correct values should be coded into tests.

Negative Tests

Now we add some tests that we expect should come out as invalid. You need both positive and negative tests to make sure the code both does what it is expected and excludes cases it is expected to exclude.

To make a negative test, I take a valid CUSIP and change any one of its constituent digits. Again, we make sure to force the actual variable to change for our test.

VB.NET
<TestMethod()>
<TestCategory("CUSIP")>
Public Sub CusipSprintInValidTestMethod_9()
    Dim cusipTest As New CusipIdentifier("852061109")
    Dim expected As Boolean = False
    Dim actual As Boolean = True

    actual = cusipTest.IsValid()

    Assert.AreEqual(expected, actual)

End Sub

The next step is to run the tests - even though we have not yet written the code that implements the validation logic. This is the key difference in test driven development - we put the tests in first and run them to check they fail.

In the case above, only the positive tests are failing because the validation only returns false. If all your tests pass before you write any business logic code, you need to check your test code and add more tests.

Fill in the Scaffold Code

Finally, the business logic is written and as soon as it is, all the tests are run against it. The final working CUSIP validator is therefore:

VB.NET
Public Class CusipIdentifier
    Inherits SecurityIdentifier
    Implements ISecurityIdentifierValidation

    Public Function IsValid() As Boolean Implements ISecurityIdentifierValidation.IsValid

        If (String.IsNullOrWhiteSpace(MyBase.Identifier)) Then
            Return False
        End If

        If (MyBase.Identifier.Length = 9) Then
            Dim checkDigitExpected As Integer = _
                        GetStringValue(MyBase.Identifier.Substring(0, 8))
            Dim checkDigitActual As Integer
            If Integer.TryParse(MyBase.Identifier.Substring(8), checkDigitActual) Then
                Return (checkDigitActual = checkDigitExpected)
            Else
                Return False
            End If
        Else
            Return False
        End If

    End Function

    Public Sub New(ByVal cusipSecurityIdentifier As String)
        MyBase.New(cusipSecurityIdentifier)
    End Sub

    Private Function GetLetterValue(ByVal letter As Char) As Integer

        Select Case letter
            Case "0"
                Return 0
            Case "1"
                Return 1
            Case "2"
                Return 2
            Case "3"
                Return 3
            Case "4"
                Return 4
            Case "5"
                Return 5
            Case "6"
                Return 6
            Case "7"
                Return 7
            Case "8"
                Return 8
            Case "9"
                Return 9
            Case "A", "a"
                Return 10
            Case "B", "b"
                Return 11
            Case "C", "c"
                Return 12
            Case "D", "d"
                Return 13
            Case "E", "e"
                Return 14
            Case "F", "f"
                Return 15
            Case "G", "g"
                Return 16
            Case "H", "h"
                Return 17
            Case "I", "i"
                Return 18
            Case "J", "j"
                Return 19
            Case "K", "k"
                Return 20
            Case "L", "l"
                Return 21
            Case "M", "m"
                Return 22
            Case "N", "n"
                Return 23
            Case "O", "o"
                Return 24
            Case "P", "p"
                Return 25
            Case "Q", "q"
                Return 26
            Case "R", "r"
                Return 27
            Case "S", "s"
                Return 28
            Case "T", "t"
                Return 29
            Case "U", "u"
                Return 30
            Case "V", "v"
                Return 31
            Case "W", "w"
                Return 32
            Case "X", "x"
                Return 33
            Case "Y", "y"
                Return 34
            Case "Z", "z"
                Return 35
            Case "*"
                Return 36
            Case "@"
                Return 37
            Case "#"
                Return 38
            Case Else
                Throw New ArgumentOutOfRangeException_
               ("letter", "Specified letter is not part of the CUSIP allowed letter list")
        End Select

    End Function

    Private Function GetStringValue(ByVal cusipString As String) As Integer
        If cusipString.Length < 8 Then
            Throw New ArgumentOutOfRangeException("cusipString", "CUSIP is too short")
        Else
            Dim sum As Integer = 0
            For letterPos As Integer = 0 To 7 Step 1
                Dim v As Integer
                If ((letterPos Mod 2) = 1) Then
                    v = 2 * GetLetterValue(cusipString.Chars(letterPos))
                Else
                    v = GetLetterValue(cusipString.Chars(letterPos))
                End If
                sum = sum + ((v \ 10) + (v Mod 10))
            Next
            Return (10 - (sum Mod 10)) Mod 10
        End If
    End Function

End Class

Running the Tests

If you have the professional or ultimate versions of Visual Studio, you will have a test menu in the IDE:

Image 1

Under this menu are options to either run the tests or to debug them - typically what is done is to run all the tests, then debug any failing tests.

If you don't have the full version of Visual Studio, you will need to run MSTest.exe from the command line.

History

  • 25th February, 2014: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer
Ireland Ireland
C# / SQL Server developer
Microsoft MVP (Azure) 2017
Microsoft MVP (Visual Basic) 2006, 2007

Comments and Discussions

 
QuestionWhat's next? Pin
Member 1042556526-Feb-14 7:52
professionalMember 1042556526-Feb-14 7:52 
AnswerRe: What's next? Pin
Duncan Edwards Jones26-Feb-14 8:35
professionalDuncan Edwards Jones26-Feb-14 8:35 
Ah - if you have a sufficient version of visual studio (I believe "Professional") there is a menu "Test" that allows you to run the tests inside the IDE

If not you need to use the MSTest.exe command line[^]

(I very much recommend including unit testing in your build automation too)

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.