Unit Testing, why test iteration
In the SDE/Test interview I noticed that candidiates we asked to write some code and then explain how to test it. All the code examples they were asked to write involved iteration. The reason for this is that you can have many different sequences to test with iteration, its why its a good one to test. One of examples there were asked to write was the InString function (or InStr function). The example I'll develop here has a few rules.
- Function is assumed to be a service to the input string, a la FCL strings.
- 'String to locate' must not be empty
- 'String to locate' must be found in full
- Only the starting character is returned if the 'string to locate' is found
- 0 based assumed.
These rules provide our testing basis and I would expect to see these in the specification for the routine. For the code I have used a hybrid sudo/VB code. The code for this would look something like.
test string 1aa2
string to test aa
If stringtotest > 0 then
teststring_letter_count = 0
matched_string_start = 0
matched_character_count = 0
stringtotest_length = stringtotest length
for each letter in stringtotest
for each letter in teststring
If stringtotest_letter = tesstring_letter then
If matched_string_start = 0 then
matched_string_start = teststring_letter_count
end if
matched_character_count + = 1
else
If matched_string_start > 0 Then
if stringtotest_length < matched_character_count
matched_string_start = 0
end if
end if
end if
teststring_letter_count += 1
next
next
Return matched_string_start
else
Return 0
end if
In this code 'test string' would be the string we are testing against (rule 1) where as 'string to test' would be the string we are looking for. In the code we obtain the length of the 'string to test'. Next we test that 'string to test' is not empty, if so return 0 (rule 2 & 5). If not then we interate through each letter in the 'test string'. As we iterate through we then iterate through the 'string to test'. Inside of this count up each iteration starting from 0 (rule 5) and add that count to teststring_letter_count. We also test each character in the string against each other, if we get a match we see if matched_string_start is zero if so then we add count 1 to count 2 (rule 4). We also increment the value of test3. If we don't get a match then see if matched_string_start is more than zero. If so then we see if the 'string to test' length is less than matched_character_count, if so then we set matched_string_start. This test ensures that we have a full string match (rule 4).
We can dry run the test of this code as follows
| stringtotest | teststring | stringtotest=teststring | teststring_letter_count | matchedstring_start | matched_character_count |
| a | 1 | False | 0 | 0 | 0 |
| a | a | True | 1 | 1 | 1 |
| a | a | True | 2 | 1 | 2 |
| a | 2 | False | 3 | 1 | 2 |
| stringtotest | teststring | stringtotest=teststring | teststring_letter_count | matchedstring_start | matched_character_count |
| a | 1 | False | 4 | 1 | 2 |
| a | a | True | 5 | 1 | 3 |
| a | a | True | 6 | 1 | 4 |
| a | 2 | False | 7 | 1 | 4 |
So here we are expecting that the first 'a' in the 'test string' is to be located at postion 1. If we change the data slightly
test string 1a2
string to test aa
| stringtotest | teststring | stringtotest=teststring | teststring_letter_count | matchedstring_start | matched_character_count |
| a | 1 | False | 0 | 0 | 0 |
| a | a | True | 1 | 1 | 1 |
| a | 2 | False | 2 | 0 | 1 |
stringtotest | teststring | stringtotest=teststring | teststring_letter_count | matchedstring_start | matched_character_count |
| a | 1 | False | 3 | 0 | 1 |
| a | a | True | 4 | 1 | 2 |
| a | 2 | False | 5 | 0 | 2 |
We expect that the 'test string' won't be located. From the rules set above we can create the unit tests and compare the actual to the expected based on our dry run data. Lets consider what we can actually test.
The assumption is that the only output is the matching character count (matchedstring_start), we could expose the counter variables to let us use them in a test but the assumption is that they are private. To get around this we could introduce some logging into our code to help record whats going on or if using VS we could step through the code as we run the unit tests (not a very automated approach though). Assumning out code is wrapped in the following function
Function MyInString(ByVal TestString, ByVal StringToTest)
Our tests would look like the following
Assert.AreEqual(1,MyInString("1aa2", "aa")
Should return 1
Assert.AreEqual(1,MyInString("1a2","aaa")
Should return 0
The next phase is test in ranges and it makes sense to move to data range testing using either MbUnits data driven tests or row based testing (or using another framework I would create some code that will let you do this). With the tests complete and passing you could of course refcator the whole code to use RegExs but with the unit tests you can be sure you code will behave in the same fasion no matter how you shape it. If anyone spots an issue or defect then let me know.