The unit testing is concept coming from TDD, and it allows you to test repeatedly your set of functions and check, whether it does not start to fall apart after few modifications.
One of the key ideas of TDD is to write failing tests first, and try to fix the issue one by one, finally reaching fully reliable code (in ideal situation).
Example
Let's say we want to write factorial function on our own (note: thinBasic Math module already provides one)
Factorial can be calculated as following:
n! = (n) * (n-1) * (n - 2) * ... * 1
It is known to be valid only for positive integeres, for zero it returns 1.
To indicate wrong input somehow, let's agree it will return -1 for such a case.
Writing the test
So, we will write the test of the possible user cases first. It is good practice (and required by attached unit) to prefix the test functions with test_.
You can see how the following code manages the testing engine.
Uses "Console"
' -- Include file for unit testing support
#INCLUDE "unitTesting.tBasicU"
' -- Main body of our testing engine
Function TBMain()
Print "Testing factorial..."
' -- Initialize testing engine
ut_Initialize()
' -- Automatically launch all possible tests
ut_LaunchTests()
' -- Report failures found in report, if any
Long nFailures = ut_GetFailureCount()
Long i
If nFailures Then
PrintL
PrintL
For i = 1 To nFailures
PrintL "#"+i
PrintL "Test: " + ut_GetFailureTestName(i)
PrintL "Assert: " + ut_GetFailureAssertType(i)
PrintL "Description: " + ut_GetFailureDescription(i)
PrintL "Comment: " + ut_GetFailureComment(i)
PrintL
Next
Else
PrintL "PASSED"
End If
' -- Save results to default file
ut_SaveLog()
' -- Release engine
ut_Release()
WaitKey
End Function
' -- Our function
Function Fact (ByVal aInput As Long) As Quad
Return 0
End Function
' -- Custom tests
Function test_Fact_NegativeInput()
ut_assertEqual( -1, fact(-1), "Test of factorial for negative value")
End Function
Function test_Fact_For0()
ut_assertEqual( 1, fact(0) , "Test of factorial for input equal to zero")
End Function
Function test_Fact_For1()
ut_assertEqual( 1, fact(1) , "Test of factorial for input equal to one")
End Function
Function test_Fact_ForGeneralPositiveValue()
ut_assertEqual(120, fact(5) , "Test of factorial for one of the possible positive values")
End Function
Function test_Fact_ForTooBigValue()
ut_assertEqual( -1, fact(21), "Test of factorial for value out of bounds, on the positive side")
End Function
The validation of returned values is handled via so called asserts. They indicate expected value and allow to add commentary. In this case, the following line:
ut_assertEqual(120, fact(5) , "fact(5)")
...means: The value of 120 is expected result of fact(5) call. The "fact(5)" is just our commentary to be used as clue in case of failing test.
If you run the code above, you will get the following flood of errors:
#1
Test: TEST_FACT_NEGATIVEINPUT
Assert: UT_ASSERTEQUAL
Description: Expected value=-1, found value=0
Comment: Test of factorial for negative value
#2
Test: TEST_FACT_FORTOOBIGVALUE
Assert: UT_ASSERTEQUAL
Description: Expected value=-1, found value=0
Comment: Test of factorial for value out of bounds, on the positive side
#3
Test: TEST_FACT_FORGENERALPOSITIVEVALUE
Assert: UT_ASSERTEQUAL
Description: Expected value=120, found value=0
Comment: Test of factorial for one of the possible positive values
#4
Test: TEST_FACT_FOR0
Assert: UT_ASSERTEQUAL
Description: Expected value=1, found value=0
Comment: Test of factorial for input equal to zero
#5
Test: TEST_FACT_FOR1
Assert: UT_ASSERTEQUAL
Description: Expected value=1, found value=0
Comment: Test of factorial for input equal to one
Note: The test test_Fact_ForTooBigValue was designed with limitations of QUAD(Int64) in mind. It is well known fact you can't go over 20! with it, because of overflow.
Building it up
Once we have a set of failing tests, we can start to fix the errors. Let's start with input validation.
Thanks to using QUAD as datatype in this model example, we know the input must not be bigger than 20 and also that factorial for negative integers is not defined.
Let's improve the Fact function this way:
Function Fact (ByVal aInput As Long) As Quad
If Not Inside(aInput, 0, 20) Then
Return -1
End If
End Function
When we run the test again:
#1
Test: TEST_FACT_FORGENERALPOSITIVEVALUE
Assert: UT_ASSERTEQUAL
Description: Expected value=120, found value=0
Comment: Test of factorial for one of the possible positive values
#2
Test: TEST_FACT_FOR0
Assert: UT_ASSERTEQUAL
Description: Expected value=1, found value=0
Comment: Test of factorial for input equal to zero
#3
Test: TEST_FACT_FOR1
Assert: UT_ASSERTEQUAL
Description: Expected value=1, found value=0
Comment: Test of factorial for input equal to one
...we can see we fixed two issues at once - test_Fact_NegativeInput and test_Fact_ForTooBigValue are gone. Hurray!
Next step
Then we can get rid of the special case of 0, by following modification:
Function Fact (ByVal aInput As Long) As Quad
If Not Inside(aInput, 0, 20) Then
Return -1
End If
If (aInput = 0) Then
Return 1
End If
End Function
When we run the test, we can see test_Fact_For0 is really gone:
#1
Test: TEST_FACT_FORGENERALPOSITIVEVALUE
Assert: UT_ASSERTEQUAL
Description: Expected value=120, found value=0
Comment: Test of factorial for one of the possible positive values
#2
Test: TEST_FACT_FOR1
Assert: UT_ASSERTEQUAL
Description: Expected value=1, found value=0
Comment: Test of factorial for input equal to one
Finish it!
Well, what about making it actually calculating the factorial?
Let's mod it to this:
Function Fact (ByVal aInput As Long) As Quad
If Not Inside(aInput, 0, 20) Then
Return -1
End If
If (aInput = 0) Then
Return 1
End If
Long i
Quad result = 1
For i = 2 To aInput
result *= i
Next
Return result
End Function
Dramatic, isn't it. Let's run the test:
Testing factorial...PASSED
Oh yeah! So now we can be sure our Fact function works correctly for input in range 1..20.
You don't have to be afraid to rewrite function Fact in other way, because in case of doubt, you can always automatically check it over and over again via the automated test.
Petr
P.S. The 3 phases of implementation are attached below, get the unitTesting.tBasicU from the first post
Bookmarks