Reputation: 642
I have a method which does the following. Question is how do I unit test this method. I am pretty new to this Python unit testing module.
The question and solution are as follows:
Given a string containing of ‘0’, ‘1’ and ‘?’ wildcard characters, generate all binary strings that can be formed by replacing each wildcard character by ‘0’ or ‘1’.
Example :
Input str = "1??0?101"
Output:
10000101
10001101
10100101
10101101
11000101
11001101
11100101
11101101
Solution:
def _print(string, index):
if index == len(string):
print(''.join(string))
return
if string[index] == "?":
# replace '?' by '0' and recurse
string[index] = '0'
_print(string, index + 1)
# replace '?' by '1' and recurse
string[index] = '1'
_print(string, index + 1)
# NOTE: Need to backtrack as string
# is passed by reference to the
# function
string[index] = '?'
else:
_print(string, index + 1)
# Driver code
if __name__ == "__main__":
string = "1??0?101"
string = list(string) #don’t forget to convert to string
_print(string, 0)
Output:
10000101
10001101
10100101
10101101
11000101
11001101
11100101
11101101
Questions:
1. Also, is there a way of returning a list as output instead of printing them out?
2. Which assert test cases are appropriate in this scenario?
3. What would be the best end to end test cases to cover in this case?
4. What could be a better approach of solving this in terms of time and space complexity?
I have tried this which doesn't seem to work:
import unittest
from wildcard import _print
class TestWildCard(unittest.TestCase):
def test_0_print(self):
print("Start wildCard _print test: \n")
result = 111
self.assertEquals(_print("1?1",0),result,"Results match")
Upvotes: 0
Views: 139
Reputation: 31379
Answers:
1: sure, instead of printing something, append the result to a list result.append('some value')
and don't forget to initialise the list at the start of your code result = []
and return it once the function is done return result
- and probably don't call the function _print
, but something like bit_strings
.
ad 1: since your function is recursive, you now also need to capture the return value and add it to the result when calling the function recursively, so result += _print(string, index + 1)
2: you should typically think of edge cases and test them separately, or group those together that really test a single aspect of your function. There is no one way to state what the test should look like - if there were, the test framework would just generate it for you.
3: same answer as 2.
Your code becomes:
def bit_strings(s, index):
result = []
if index == len(s):
result.append(''.join(s))
return result
if s[index] == "?":
# replace '?' by '0' and recurse
s[index] = '0'
result += bit_strings(s, index + 1)
# replace '?' by '1' and recurse
s[index] = '1'
result += bit_strings(s, index + 1)
# NOTE: Need to backtrack as string
# is passed by reference to the
# function
s[index] = '?'
else:
result += bit_strings(s, index + 1)
return result
# Driver code
if __name__ == "__main__":
x = "1??0?101"
xl = list(x) #don’t forget to convert to string
print(bit_strings(xl, 0))
There's more efficient ways of doing this, but I just modified your code in line with the questions and answers.
I've renamed string
to s
, since string
is a bit confusing, reminding others of the type or shadowing the (built-in) module.
As for the unit test:
import unittest
from wildcard import bit_strings
class TestWildCard(unittest.TestCase):
def test_0_print(self):
print("Start wildCard _print test: \n")
# you only had one case here and it's a list now
result = ['101', '111']
# user assertEqual, not Equals
# you were passing in a string, but your code assumed a list, so list() added
self.assertEqual(bit_strings(list("1?1"), 0), result, "Results match")
When using an environment like PyCharm, it helps to call the file test<something>.py
(i.e. have test
in the name), so that it helps you run the unit tests more easily.
Two alternate solutions as requested in comment (one still recursive, just a lot more concise, the other not recursive but arguably a bit wasteful with result lists - just two quickies):
from timeit import timeit
def unblank_bits(bits):
if not bits:
yield ''
else:
for ch in '01' if bits[0] == '?' else bits[0]:
for continuation in unblank_bits(bits[1:]):
yield ch + continuation
print(list(unblank_bits('0??100?1')))
def unblank_bits_non_recursive(bits):
result = ['']
for ch in bits:
if ch == '?':
result = [s+'0' for s in result] + [s+'1' for s in result]
else:
result = [s+ch for s in result]
return result
print(list(unblank_bits_non_recursive('0??100?1')))
print(timeit(lambda: list(unblank_bits('0??100?1'))))
print(timeit(lambda: list(unblank_bits_non_recursive('0??100?1'))))
This solution doesn't move between lists and strings, as there is no need and doesn't manipulate the input values. As you can tell the recursive one is a bit slower, but I prefer it for readability. The output:
['00010001', '00010011', '00110001', '00110011', '01010001', '01010011', '01110001', '01110011']
['00010001', '01010001', '00110001', '01110001', '00010011', '01010011', '00110011', '01110011']
13.073874
3.9742709000000005
Note that your own solution ran in about 8 seconds using the same setup, so the "improved version" I suggested is simpler, but not faster, so you may prefer the latter solution.
Upvotes: 1