RSS
 

#21: Test-driven development (Unit Test)

02 Oct

In computer programming, unit testing is a method by which individual units of source code are tested to determine if they are fit for use. A unit is the smallest testable part of an application. In procedural programming a unit may be an individual function or procedure. Unit tests are created by programmers or occasionally by white box testers.

QUESTION:
Create a console application that takes in the coordinates of two points in a 2-dimensional plane and outputs the linear equation joining the two points similar to the one shown in Part I. In particular, the equation should be in the following form:
y=mx+b
Where m is the slope of the line and b is the y-intercept. Also, create unit test cases using the Microsoft Unit Testing Framework to thoroughly test your console application.
The format of the input parameter and output value are entirely up to you. Be sure to properly document all your code and note all of your assumptions in your main application as well as your unit test suite.

ANSWER:
To create the console application seems very easy, but there are some potential bugs you may want to consider when you write the code:
1, the input points’ coordinates contain no character or non-numeric character(s): wrong input, ask the user to re-enter the coordinate.
2, the x and y coordinates for pointA and pointB are the same: wrong input, ask the user to re-enter the x and y coordinates for the 2 points.
3, horizontal line: y=constant
4, vertical line: x=constant
5, m=1: the equation should be “y=x+b” instead of “y=1x+b”
6, m=-1: the equation should be “y=-x+b” instead of “y=-1x+b”
7, b=0: the equation should be “y=mx” instead of “y=mx+0″
8, b<0: e.g., b=-3, the equation should be "y=mx-3" instead of "y=mx+-3"
9, m and/or b are/is (a) decimal number(s): the decimal places should be limited as per spec (I assume the requirement is to limit the decimal places to 2 places).

First I wrote the code in VBScript, you can click here to download the script. Paste the VBScript in a notepad, and save it as “anyname.vbs”, then double click on the .vbs file to run it.

In the above VBScript, everything is handled in pop-up boxes (e.g., Input box and Message box), which is not a truly console application (black screen, DOS like looking). However, If you have Microsoft Visual Studio installed on your computer, then you can create a real console application by VB.Net. VBScript, VBA and VB.Net are very similar, if you know how to write VBScript or VBA, and use Google search, then you should be able to transfer your script to VB.Net.

A copy of the VB.Net code for the console application is pasted below. You will notice that the code below is very different than the VBScript mentioned above. There is only one function in the VBScript, which is called “Function OutputLinearEquation”. This function has been broken into 3 different functions in the VB.Net code. Why? All because the unit test.

All the functions that has logic should be tested by unit test, but in order to write the unit test, each function should only handle one small/simple thing, otherwise the function wont’ be unit test testable. In the unit test, there is expected values, and actual values, and you suppose to compare the expected and actual values to see if they are the same or not. If the function you wrote is complicated, and handles multiple things, such as the “OutputLinearEquation” function, then it will be impossible to identify which is the expected/actual value.

Transfer the VBScript to VB.Net wasn’t difficult, just some easy syntax changes, but when I start to write the unit test, I went back to the VB.Net code 3-5 times, to break the “OutputLinearEquation” function into multiple functions, each only handle a single thing. It took me 3 times more to write the unit test than to write the actual code. After the unit test was done, I fully understand why the developers in my company really don’t like to write unit tests – it takes too much time to write the unit test…

Think back, I should really think about the unit test first before I start to write the code. In that way, I may spend much less time. Actually, someone already figure this out long time before me, and they call this approach as “test-driven development”.

Test-driven development (TDD) is a software development process that relies on the repetition of a very short development cycle: first the developer writes a failing automated test case that defines a desired improvement or new function, then produces code to pass that test and finally refactors the new code to acceptable standards. Kent Beck, who is credited with having developed or ‘rediscovered’ the technique, stated in 2003 that TDD encourages simple designs and inspires confidence. For more information about Test-driven development, please read post#30.

A copy of the VB.Net code for the console application is pasted below.

Module Module1

    Public x1 As Decimal, y1 As Decimal, x2 As Decimal, y2 As Decimal

    Sub Main()
        Call Input2points()
        Console.WriteLine("The linear equation joins " & "pointA(" & x1 & "," & y1 & ") and pointB(" & x2 & "," & y2 & ") is" & vbCrLf & GetLinearEquation(x1, y1, x2, y2))
        Console.WriteLine("Please click [Enter] key to exit.")
        Console.ReadLine()
    End Sub

'input 2 points' x and y coordinates
    Public Sub Input2points()
        Call Inputx1()
        Call Inputy1()
        Call Inputx2()
        Call Inputy2()
        'wrong input: the 2 points should not be the same
        If x1 = x2 And y1 = y2 Then
            Console.WriteLine("PointA(" & x1 & "," & y1 & ") and pointB(" & x2 & "," & y2 & ") are the same." & vbCrLf & "Please re-enter the X and Y coordinates for pointA and pointB.")
            Input2points()
        End If
    End Sub

    'handle wrong input, i.e., non-numeric character(s) or no character was entered
    Public Sub Inputx1()
        Dim orix1 As String
        Try
            Console.WriteLine("Please enter the X coordinate for pointA:")
            orix1 = Console.ReadLine
            x1 = Convert.ToDecimal(orix1)
        Catch ex As Exception
            Console.WriteLine("Non-numeric character or No character was entered.")
            Inputx1()
        End Try
    End Sub
    Public Sub Inputy1()
        Dim oriy1 As String
        Try
            Console.WriteLine("Please enter the Y coordinate for pointA:")
            oriy1 = Console.ReadLine
            y1 = Convert.ToDecimal(oriy1)
        Catch ex As Exception
            Console.WriteLine("Non-numeric character or No character was entered.")
            Inputy1()
        End Try
    End Sub
    Public Sub Inputx2()
        Dim orix2 As String
        Try
            Console.WriteLine("Please enter the X coordinate for pointB:")
            orix2 = Console.ReadLine
            x2 = Convert.ToDecimal(orix2)
        Catch ex As Exception
            Console.WriteLine("Non-numeric character or No character was entered.")
            Inputx2()
        End Try
    End Sub
    Public Sub Inputy2()
        Dim oriy2 As String
        Try
            Console.WriteLine("Please enter the Y coordinate for pointB:")
            oriy2 = Console.ReadLine
            y2 = Convert.ToDecimal(oriy2)
        Catch ex As Exception
            Console.WriteLine("Non-numeric character or No character was entered.")
            Inputy2()
        End Try
    End Sub

    'get the linear equation which joins the 2 points
    Public Function GetLinearEquation(ByVal x1, ByVal y1, ByVal x2, ByVal y2) As String
        If x1 <> x2 Then
            Dim params As Array
            Try
                params = CalculateLineParams(x1, y1, x2, y2)
            Catch ex As Exception
                GetLinearEquation = "Error! Cannot calculate m or b." & ex.ToString()
            End Try
            'use 2 decimals places in the output linear equationn
            Dim m As Decimal, b As Decimal
            m = FormatNumber(params(0), 2)
            b = FormatNumber(params(1), 2)
            GetLinearEquation = FormatLinearEquation(m, b)
        Else 'special case: vertical line
            GetLinearEquation = "x=" & x1
        End If
    End Function

    'caculate the m and b value for the linear equation y=mx+b
    Public Function CalculateLineParams(ByVal x1, ByVal y1, ByVal x2, ByVal y2) As Array
        If x1 = x2 Then
            Throw New ArgumentException("x1 = x2. It is a vertical line.")
        Else
            Dim params(2) As Decimal
            params(0) = (y2 - y1) / (x2 - x1)    'original m
            params(1) = y1 - params(0) * x1   'original b
            Return params
        End If
    End Function

    'format the linear equation y=mx+b which joins the 2 points
    Public Function FormatLinearEquation(ByVal m, ByVal b) As String
        If m <> 0 Then
            'normal case: y=mx+b
            If m <> 1 And m <> -1 And b > 0 Then
                FormatLinearEquation = "y=" & m & "x+" & b
                'special case: y=mx+0 should be displayed as y=mx
            ElseIf m <> 1 And m <> -1 And b = 0 Then
                FormatLinearEquation = "y=" & m & "x"
                'special case: y=mx+-b should be displayed as y=mx-b
            ElseIf m <> 1 And m <> -1 And b < 0 Then
                FormatLinearEquation = "y=" & m & "x" & b
                'special case: y=1x+b should be displayed as y=x+b
            ElseIf m = 1 And b > 0 Then
                FormatLinearEquation = "y=x+" & b
                'special case: y=1x+0 should be displayed as y=x
            ElseIf m = 1 And b = 0 Then
                FormatLinearEquation = "y=x"
                'special case: y=1x+-b should be displayed as y=x-b
            ElseIf m = 1 And b < 0 Then
                FormatLinearEquation = "y=x" & b
                'special case: y=-1x+b should be displayed as y=-x+b
            ElseIf m = -1 And b > 0 Then
                FormatLinearEquation = "y=-x+" & b
                'special case: y=-1x+0 should be displayed as y=-x
            ElseIf m = -1 And b = 0 Then
                FormatLinearEquation = "y=-x"
                'special case: y=-1x+-b should be displayed as y=-x-b
            ElseIf m = -1 And b < 0 Then
                FormatLinearEquation = "y=-x" & b
            End If
        Else 'special case: horizontal line
            FormatLinearEquation = "y=" & b
        End If
    End Function

End Module

I also created 5 unit test cases to test the 3 functions in the console application. The unit test cases were created in Microsoft Visual Studio by VB.Net too, and the Microsoft Unit Testing Framework is used:
A copy of the code for the 5 unit test cases is pasted below:

Imports System
Imports Microsoft.VisualStudio.TestTools.UnitTesting
Imports ConsoleApplication1

' a lot of code automatically generated by Microsoft Visual Studio
' these code won't show in this post

    'A test for Function CalculateLineParams when x1 <> x2
     _
    Public Sub CalculateLineParamsNormalCaseTest()
        Dim R = New Random
        Dim x1 As Decimal = R.Next + R.NextDouble
        Dim y1 As Decimal = R.Next + R.NextDouble
        Dim x2 As Decimal = R.Next + R.NextDouble
        Do While x2 = x1
            x2 = R.Next + R.NextDouble
        Loop
        Dim y2 As Decimal = R.Next + R.NextDouble
        Dim expected(1) As Decimal
        expected(0) = y1
        expected(1) = y2
        Dim actual(1) As Decimal, params(1) As Decimal
        params = Module1.CalculateLineParams(x1, y1, x2, y2)
        actual(0) = params(0) * x1 + params(1)
        actual(1) = params(0) * x2 + params(1)
        Assert.AreEqual(actual(0), expected(0))
        Assert.AreEqual(actual(1), expected(1))
    End Sub

    'A test for Function CalculateLineParams when x1 = x2
     _
     _
    Public Sub CalculateLineParamsVerticalLineTest()
        Dim R = New Random
        Dim x1 As Decimal = R.Next + R.NextDouble
        Dim y1 As Decimal = R.Next + R.NextDouble
        Dim x2 As Decimal = x1
        Dim y2 As Decimal = R.Next + R.NextDouble
        Dim actual As Array = CalculateLineParams(x1, y1, x2, y2)
    End Sub

    'A test for Function FormatLinearEquation
     _
    Public Sub FormatLinearEquationTest()
        Dim R = New Random
        Dim m As Decimal, b As Decimal
        Dim expected As String, actual As String
        'special case: horizontal line
        m = 0
        b = R.Next + R.NextDouble
        expected = "y=" & b
        actual = Module1.FormatLinearEquation(m, b)
        Assert.AreEqual(expected, actual)
        'normal case: y=mx+b
        m = R.Next + R.NextDouble
        Do While m = 0 Or m = 1 Or m = -1
            m = R.Next + R.NextDouble
        Loop
        b = R.Next(0) + R.NextDouble()
        expected = "y=" & m & "x+" & b
        actual = Module1.FormatLinearEquation(m, b)
        Assert.AreEqual(expected, actual)
        'special case: y=mx+0 should be displayed as y=mx
        b = 0
        expected = "y=" & m & "x"
        actual = Module1.FormatLinearEquation(m, b)
        Assert.AreEqual(expected, actual)
        'special case: y=mx+-b should be displayed as y=mx-b
        b = -R.Next(0) - R.NextDouble()
        expected = "y=" & m & "x" & b
        actual = Module1.FormatLinearEquation(m, b)
        Assert.AreEqual(expected, actual)
        'special case: y=1x+b should be displayed as y=x+b
        m = 1
        b = R.Next(0) + R.NextDouble()
        expected = "y=x+" & b
        actual = Module1.FormatLinearEquation(m, b)
        Assert.AreEqual(expected, actual)
        'special case: y=1x+0 should be displayed as y=x
        b = 0
        expected = "y=x"
        actual = Module1.FormatLinearEquation(m, b)
        Assert.AreEqual(expected, actual)
        'special case: y=1x+-b should be displayed as y=x-b
        b = -R.Next(0) - R.NextDouble()
        expected = "y=x" & b
        actual = Module1.FormatLinearEquation(m, b)
        Assert.AreEqual(expected, actual)
        'special case: y=-1x+b should be displayed as y=-x+b
        m = -1
        b = R.Next(0) + R.NextDouble()
        expected = "y=-x+" & b
        actual = Module1.FormatLinearEquation(m, b)
        Assert.AreEqual(expected, actual)
        'special case: y=-1x+0 should be displayed as y=-x
        b = 0
        expected = "y=-x"
        actual = Module1.FormatLinearEquation(m, b)
        Assert.AreEqual(expected, actual)
        'special case: y=-1x+-b should be displayed as y=-x-b
        b = -R.Next(0) - R.NextDouble()
        expected = "y=-x" & b
        actual = Module1.FormatLinearEquation(m, b)
        Assert.AreEqual(expected, actual)
    End Sub

    'A test for Function GetLinearEquation when x1 <> x2
     _
    Public Sub GetLinearEquationNormalCaseTest()
        Dim R = New Random
        Dim x1 As Decimal = R.Next + R.NextDouble
        Dim y1 As Decimal = R.Next + R.NextDouble
        Dim x2 As Decimal = R.Next + R.NextDouble
        Do While x2 = x1
            x2 = R.Next + R.NextDouble
        Loop
        Dim y2 As Decimal = R.Next + R.NextDouble
        Dim expected As String, actual As String
        expected = "x=" & x1
        actual = Module1.GetLinearEquation(x1, y1, x2, y2)
        Assert.AreNotEqual(expected, actual)
    End Sub

    'A test for Function GetLinearEquation when x1 = x2
     _
    Public Sub GetLinearEquationVerticalLineTest()
        Dim R = New Random
        Dim x1 As Decimal = R.Next + R.NextDouble
        Dim y1 As Decimal = R.Next + R.NextDouble
        Dim x2 As Decimal = x1
        Dim y2 As Decimal = R.Next + R.NextDouble
        Dim expected As String, actual As String
        expected = "x=" & x1
        actual = Module1.GetLinearEquation(x1, y1, x2, y2)
        Assert.AreEqual(expected, actual)
    End Sub
End Class
 
No Comments

Posted by Jia Qi in Unit Test

 

Tags: , , , , , , , , , , , , , , , ,

Leave a Reply