Click here to Skip to main content
11,414,670 members (66,046 online)
Click here to Skip to main content

Test driven development example - CUSIP validation

, 25 Feb 2014 CPOL
Rate this:
Please Sign up or sign in to vote.
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 to create an empty stub of a class that will do the actual logic of the CUSIP validation:-
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:-  

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:-

    <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.

    '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 initialise 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.

    <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:-

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 

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

License

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

Share

About the Author

Duncan Edwards Jones
Software Developer (Senior)
Ireland Ireland
C# / SQL Server developer
Microsoft MVP 2006, 2007
Visual Basic .NET
Follow on   Twitter   LinkedIn

Comments and Discussions

 
QuestionWhat's next? Pin
Member 1042556526-Feb-14 8:52
memberMember 1042556526-Feb-14 8:52 
AnswerRe: What's next? Pin
Duncan Edwards Jones26-Feb-14 9:35
professionalDuncan Edwards Jones26-Feb-14 9:35 

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

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

| Advertise | Privacy | Terms of Use | Mobile
Web04 | 2.8.150427.4 | Last Updated 25 Feb 2014
Article Copyright 2014 by Duncan Edwards Jones
Everything else Copyright © CodeProject, 1999-2015
Layout: fixed | fluid