简体   繁体   中英

Weird behavior of nosetest

I try to use nose test But when i run the testcase below

import unittest

class TestSuite(unittest.TestCase):
    b = []

    def setUp(self):
        self.b.extend([10, 20])

    def tearDown(self):
        self.b = []

    def test_case_1(self):
        self.b.append(30)
        assert len(self.b) == 3
        assert self.b == [10, 20, 30]

    def test_case_2(self):
        self.b.append(40)
        assert len(self.b) == 3
        assert self.b == [10, 20, 40]

But all test cases is not pass

$> nosetest test_module.py
.F
======================================================================
FAIL: test_case_2 (test_module2.TestSuite)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/knt/test_module2.py", line 19, in test_case_2
    assert len(self.b) == 3
AssertionError

----------------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (failures=1)

What 's happend ??? I expect after run test_case_1, tearDown will be called, so self.b is [] . So with next test case test_case_2 , setUp run and self.b is [10, 20] .

But in fact, at setUp value of self.b is [10, 20, 30] .

I dont know why. I think there must be some problems with statement self.b = [] .

Anything related pointers, I guess? I still didn't figure it out, but i find way to fix this bug. Just change self.b = [] to del self.b[:] .

Anyone can help me find out the problem? Thank you so much.

As far as I can tell the problem maybe with the way unitests works and how class fields work in python here is a simple test:

class A:
    b = []
    def reset(self):
        self.b = []

a = A()    
a.b.append(3) # we are actually accessing the class variable here
print A.b is a.b # True
print a.b # [3] same here
a.reset() # We just hid the class variable with our own which points to []
print A.b is a.b # False as expected.
print a.b # [] we think its being clear but rather we are using our own not the class variable

b = A()
print b.b # [3] b here is using the class that a previously modified but is no longer pointing to
print b.b is A.b # True

# Also note
c = A()
d = A()
print c.b is d.b # True, since both are using the same class variable.

I think unittest is creating the object multiple times, for each test function, creates object, fires setup which access the class variable, the test runs, calls teardown which simply hides it, creates another object, calls setup which access the same class variable that the previous object modified and didn't affect since its tear down simply created a new instance binded to self, hiding the class version.

we always declare member fields inside the __init__ using self.

def __init__(self):
    self.b = []

this way every instance will have its own copy, though we can't do this here since we are inheriting from unittest.TestCase thats why we have setUp

import unittest
class TestSuite(unittest.TestCase):
    def setUp(self):
        self.b = [10, 20]

    def tearDown(self):
        self.b = []

    def test_case_1(self):
        self.b.append(30)
        assert len(self.b) == 3
        assert self.b == [10, 20, 30]

    def test_case_2(self):
        self.b.append(40)
        assert len(self.b) == 3
        assert self.b == [10, 20, 40]

The problem is your class attribute in line 2:

b = []

That is an attribute on the class TestSuite . Nosetests creates a new instance of the class TestSuite , and then calls setUp . In setUp , you modify the class attribute on the class TestSuite :

self.b.extend([10, 20])

Then, in tearDown , after your first test has been run, you create a new list and assign it to a new instance attribute which is also happens to be called b :

self.b = []

This does not modify the class attribute at all. Further attempts to access self.b from this instance will return the instance attribute, not the class attribute.

But this has no effect, because the very next thing that happens is that nosetests throws away the current instance of TestSuite , including your new, empty list in the instance attribute b that you have set in tearDown .

Then nosetests creates a brand new instance of the class TestSuite , to run your second test. But the class TestSuite still has a b class attribute containing [10, 20, 30] , because it was modified while your first test was running.

Then nosetests setUp method runs, adds 10 and 20 to TestSuite 's class attribute b . Then your second test runs, adds 40 to TestSuite 's class attribute, and your second test fails, because it finds six items in that class attribute:

[10,20,30,10,20,40]

The reason that del self.b[:] works is because, just like append , del is modifying the class attribute, rather than creating a new instance attribute.

Make sure you understand the difference between classes and instances, and between class attributes and instance attributes, otherwise you will be plagued with similar problems for as long as you are working with Python.

Well, try to replace the:

self.b.extend([10,20])

with

self.b = [10,20]

and maybe throw out the class variable, you don't need it and it might be the reason this happens.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM