I'm unit testing classes in Python using unittest
. As I understand it, unittest
calls the setUp
function before each test so that the state of the unit test objects are the same and the order the test are executed wouldn't matter.
Now I have this class I'm testing...
#! usr/bin/python2
class SpamTest(object):
def __init__(self, numlist = []):
self.__numlist = numlist
@property
def numlist(self):
return self.__numlist
@numlist.setter
def numlist(self, numlist):
self.__numlist = numlist
def add_num(self, num):
self.__numlist.append(num)
def incr(self, delta):
self.numlist = map(lambda x: x + 1, self.numlist)
def __eq__(self, st2):
i = 0
limit = len(self.numlist)
if limit != len(st2.numlist):
return False
while i < limit:
if self.numlist[i] != st2.numlist[i]:
return False
i += 1
return True
with the following unit tests...
#! usr/bin/python2
from test import SpamTest
import unittest
class Spammer(unittest.TestCase):
def setUp(self):
self.st = SpamTest()
#self.st.numlist = [] <--TAKE NOTE OF ME!
self.st.add_num(1)
self.st.add_num(2)
self.st.add_num(3)
self.st.add_num(4)
def test_translate(self):
eggs = SpamTest([2, 3, 4, 5])
self.st.incr(1)
self.assertTrue(self.st.__eq__(eggs))
def test_set(self):
nl = [1, 4, 1, 5, 9]
self.st.numlist = nl
self.assertEqual(self.st.numlist, nl)
if __name__ == "__main__":
tests = unittest.TestLoader().loadTestsFromTestCase(Spammer)
unittest.TextTestRunner(verbosity = 2).run(tests)
This test fails for test_translate.
I can do two things to make the tests succeed:
(1) Uncomment the second line in the setUp function. Or,
(2) Change the names of the tests such that translate
occurs first. I noticed that unittest
executes tests in alphabetical order. Changing translate
to, say, atranslate
so that it executes first makes all tests succeed.
For (1), I can't imagine how this affects the tests since at the very first line of setUp
, we create a new object for self.st . As for (2), my complaint is similar since, hey, on setUp
I assign a new object to self.st
so whatever I do to self.st
in test_set
shouldn't affect the outcome of test_translate
.
So, what am I missing here?
Without studying the detais of your solution, you should read the Default Parameter Values in Python by Fredrik Lundh.
It is likely that it explains your problem with your empty list as a default argument. The reason is that the list is empty only for the first time unless you make it empty explicitly later. The initialy empty default list is the single instance of the list type that is reused when no explicit argument is passed.
It is good idea to read the above article to fix your thinking about the default arguments. The reasons are logical, but may be unexpected.
The generally recommended fix is to use None
as the default value of the __init__
and set the empty list inside the body if the argument is not passed, like this:
class SpamTest(object):
def __init__(self, numlist=None):
if numlist is None:
numlist = [] # this is the new instance -- the empty list
self.__numlist = numlist
This is due to the way default parameters behave in Python when using Mutable objects like lists: Default Parameter Values in Python.
In the line:
def __init__(self, numlist = []):
The default parameter for numlist is only evaluated once so you only have one instance of the list which is shared across all instance of the SpamTest
class.
So even though the test setUp
is called for every test it never creates a fresh empty list, and your tests which work upon that list instance end up stepping on each others toes.
The fix is to have something like this instead, using a non-mutable object like None
:
def __init__(self, numlist = None):
if numlist is None:
numlist = []
self.__numlist = numlist
The reason it works when setting the property is that you provide a brand new empty list there, replacing the list created in the constructor.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With