How to test variable scoped in file, from unit test - python

I have a program like this
class A():
def __init__(self):
self.foo = x
if __name__ == '__main__':
x = 96
a=A()
print(a.foo)
When this runs from the shell "python foo.py" it prints out 96
I also have test_foo.py
import foo
import unittest
class TestFoo(unittest.TestCase):
def test1(self):
x=37
a=foo.A()
self.assertEqual(a.foo, 37)
if __name__ == '__main__':
unittest.main()
When I run this test_foo.py from the shell I get
$ python test_foo.py
E
======================================================================
ERROR: test1 (__main__.TestFoo)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test_foo.py", line 8, in test1
a=foo.A()
File "/home/zzz/foo.py", line 3, in __init__
self.foo = x
NameError: global name 'x' is not defined
----------------------------------------------------------------------
Ran 1 test in 0.000s
FAILED (errors=1)
So my question is, is it possible to test foo.py from test_foo.py, to set x and see it being used in the class A. f Without altering the foo.py program
Obviously, this is a simplified version of a real program
I get the same results with python 3.6 and 2.7
I have tried using various combinations of global but didn't find a way using this

Your x variable is defined inside the if block that checks if foo.py is being run as the main program, so when foo.py is imported by test_foo.py, x would not be defined, hence the error.
You should define x outside the if block instead:
x = 96
class A():
def __init__(self):
self.foo = x
if __name__ == '__main__':
a=A()
print(a.foo)
And in test_foo.py, if you want to override the value of the x of foo, you should do foo.x=37 instead of x=37 since x would otherwise be a local variable to test1.

You need to change it to:
class A():
def __init__(self,x):
self.foo = x
and in the test:
class TestFoo(unittest.TestCase):
def test1(self):
x=37
a=foo.A(x)
self.assertEqual(a.foo, 37)

You make access to variable x in global scope here:
self.foo = x
When you run script directly __name__ is 'main', than you set global variable x and than construct A().
When you import foo module code under if is not executed. There is no variable x in global scope nor in any outer scope.

Related

Test for instantiation of a mocked class in Python?

I want to make my tests "real" unittests in a strict meaning. There shouldn't be any dependencies. All dependencies should be mocked.
The class Bar in module bar need to be tested. But in some situations it will have a member of type Foo from module foo. The goal is that the unittest does not have to import foo or foo.Foo.
My mocking seems to work. But I'm not sure how to test if Bar() now does have tried to instantiate foo.Foo(). My assert_called() in the below example failed with Expected 'mock' to have been called.. And that assert also doesn't give me all information's I want.
This is the test code:
#!/usr/bin/env python3
import unittest
from unittest import mock
import bar
# The goal ist to test "Bar" wihtout importing "foo.Foo" as an extra dependency
class MyTest(unittest.TestCase):
def test_bar(self):
mock_foo = mock.Mock()
bar.foo = mock_foo
b = bar.Bar(7)
print('')
print(f'{mock_foo=}')
print(f'{b.y=}')
# Of course this doesn't work, because "foo" is not imported
# self.assertIsInstance(b.y, foo.Foo)
print(mock_foo.__dict__)
mock_foo.assert_called() # failes
def test_int(self):
b = bar.Bar(8)
self.assertIsInstance(b.y, int)
if __name__ == '__main__':
unittest.main()
Here comes module bar:
import foo
class Bar:
def __init__(self, y):
if y == 7:
self.y = foo.Foo(7)
else:
self.y = y
And this is module foo:
class Foo:
def __init__(self, x):
self.x = x
When you're mocking, you should really be using mock.patch() to do your patching.
consider, instead:
#!/usr/bin/env python3
import sys
import unittest
from unittest import mock
import bar
class MyTest(unittest.TestCase):
#mock.patch('foo.Foo')
def test_bar(self, foo_mock):
b = bar.Bar(7)
# This _does_ work, because the patch decorator gives us the patched
# mock.
# this is something you should _never_ include in a proper unit test,
# but here to demonstrate the patching
self.assertIs(foo_mock, sys.modules['foo'].Foo)
foo_mock.assert_called_once_with(7)
def test_no_foo(self):
for test_val in [0, True, False, None, 13, 666]:
with self.subTest(test_val=test_val):
b = bar.Bar(test_val)
self.assertEqual(b.y, test_val)
if __name__ == '__main__':
unittest.main()

Python - Accessing object variables within a module

I have a module (a python file, that is) containing different classes with functions, here called 'Experiment_1'. When running it as the main file, all objects can access all variables - object_2 functions can access variables in object_1, and so on:
# Experiment_1
class A():
def process1(self):
self.x=10
def process2(self):
self.y=20
class B():
def addition(self):
summed=object_1.x+object_1.y
print(summed)
if __name__ == '__main__':
object_1=A()
object_2=B()
object_1.process1()
object_1.process2()
object_2.addition()
Next, I attempt to run this in file 'Experiment_2' as an imported module:
# Experiment_2
from Experiment_1 import *
import Experiment_1
object_1=A()
object_2=B()
object_1.process1()
object_1.process2()
object_2.addition()
And get the error message:
File "C:\Program Files\Sublime Text 3\Experiment_2.py", line 10, in <module>
object_2.addition()
File "C:\Program Files\Sublime Text 3\Experiment_1.py", line 10, in addition
summed=object_1.x+object_1.y
NameError: name 'object_1' is not defined
Thus, object_2 can no longer access the variables of object_1. I have been searching a lot to find a solution to this but may be using the wrong keywords or simply lack the understanding to recognize the answer when I see it - can anyone give me a hint how to proceed?
Thanks in advance!
When you import a module, the block starting with if __name__ == "__main__": does not get executed as the name variable of the module is set to the name you imported it ("Experiment_1" in this case). From official documentation:
When a Python module or package is imported, name is set to the module’s name. Usually, this is the name of the Python file itself without the .py extension
If you'd like your code to get executed regardless of it is imported or run directly from command line, just place it at the bottom.
The issue is your object instantiation in Experiment_1.py. The code inside the below block only get executed if you run the file as a script and NOT when you import it.
if __name__ == '__main__':
pass
So in your object of class B, the below statement in addition method,
#Experiment_1.py
summed=object_1.x+object_1.y
see object_1 as not defined, because it is only instantiated in the if __name__ == '__main__': block which is not executed when importing the module.
See this question: What does if __name__ == "__main__": do?
And for the solution, If you wish to use the module structure, the work around is to pass the object to the addition method.
#Experiment_1.py
class A():
def process1(self):
self.x = 10
def process2(self):
self.y = 20
class B():
def addition(self, obj):
summed = obj.x + obj.y
print(summed)
then in your next file,
from Experiment1 import *
# import Experiment1
object_1=A()
object_2=B()
object_1.process1()
object_1.process2()
object_2.addition(object_1)
Not tested, but I think the following script is the solution, because you need to inherit attributes. Test changes (on first module) and give a response, please.
class A():
def __init__(self):
self.x = 0
self.y = 0
def process1(self):
self.x=10
def process2(self):
self.y=20
class B(A):
def __init__(self):
A.__init__(self)
def addition(self):
summed = self.x + self.y
print(summed)
if __name__ == '__main__':
object_1 = A()
object_2 = B()
object_1.process1()
object_1.process2()
object_2.addition()
Edit
Sorry, I don't realize that B can do all purpose. This is the good solution:
experiment1.py
class A:
def __init__(self):
self.x = 0
self.y = 0
def process1(self):
self.x = 10
def process2(self):
self.y = 20
class B(A):
def __init__(self):
A.__init__(self)
def addition(self):
summed = self.x + self.y
print(summed)
if __name__ == '__main__':
object_1 = B()
object_1.process1()
object_1.process2()
object_1.addition()
experiment2.py
from Experiment1 import *
if __name__ == '__main__':
object_1 = B()
object_1.process1()
object_2.process2()
object_2.addition()
Explaination: because B class inherits methods and attributes of A class, you don't need instantiate A class. All the work can be run by B class.
As other user said, in B class you are using global variable declared in main section that is unreachable when you import module, this is the principle of the if __name__ == '__main__':

Mock instance attributes without instance

I would like to test by mocking an instance's attribute, but I do not have access to the instance beforehand. How can I mock the attribute without it? Here is my minimum reproducible code.
# test.py
class Foo:
def __init__(self, x):
self.x = x
def bar():
return Foo(1).x + 1
def test_bar(mocker):
mocker.patch('test.Foo.x', 2)
assert bar() == 3
$ pytest test.py
FAILED test.py::test_bar - AttributeError: <class 'test.Foo'> does not have the attribute 'x'
This makes sense since the Foo class doesn't have an x, only instances. If I add a kwarg mocker.patch('test.Foo.x', 2, create=True) I then get this
$ pytest test.py
FAILED test.py::test_bar - assert 2 == 3
since Foo.x will get mocked but overridden when the instance later sets self.x = x.
The standard way to do this kind of test is to mock the entire class, like so:
def test_bar(mocker):
mock_foo = mocker.MagicMock(name='Foo')
mocker.patch('test.Foo', new=mock_foo)
mock_foo.return_value.x = 2
assert bar() == 3
mock_foo.assert_called_once_with(1)
So in this case, you mock the entire Foo class.
Then, there is this syntax mock_foo.return_value.x = 2: where mock_foo.return_value simply means the return object when calling Foo(1) - which is your object. Since we mocked the entire class - the __init__ method does nothing - so you need to set the x attribute on your own.
Also note the mock_foo.assert_called_once_with(1) - which checks that Foo(1) was called with the right parameter.
p.s.
To simplify this kind of mocking, I created a pytest fixture called pytest-mock-generator. You can use it to help you create the mocks and asserts.
You start with using the mg fixture to analyze your bar method so it locates the relevant mocks for you:
def test_bar(mocker, mg):
mg.generate_uut_mocks(bar)
When this test method is executed, it generates the following code (prints it to the console and copies it to your clipboard for quick usage):
# mocked dependencies
mock_Foo = mocker.MagicMock(name='Foo')
mocker.patch('test.Foo', new=mock_Foo)
You then modify the test function and execute the bar method and use the generate_asserts capability:
def test_bar(mocker, mg):
# mocked dependencies
mock_Foo = mocker.MagicMock(name='Foo')
mocker.patch('test.Foo', new=mock_Foo)
bar()
mg.generate_asserts(mock_Foo)
This gives you the following output:
assert 1 == mock_Foo.call_count
mock_Foo.assert_called_once_with(1)
mock_Foo.return_value.x.__add__.assert_called_once_with(1)
You don't need most of it, but you can keep the second line for your assert and the modify the third line so x has the right value:
def test_bar(mocker):
mock_foo = mocker.MagicMock(name='Foo')
mocker.patch('test.Foo', new=mock_foo)
mock_foo.return_value.x = 2
assert bar() == 3
mock_foo.assert_called_once_with(1)

Magic mock assert_called_once vs assert_called_once_with weird behaviour

I am noticing a weird behavior with assert_called_once and assert_called_once_with in python. This is my real simple test:
File module/a.py
from .b import B
class A(object):
def __init__(self):
self.b = B("hi")
def call_b_hello(self):
print(self.b.hello())
File module/b.py
class B(object):
def __init__(self, string):
print("created B")
self.string = string;
def hello(self):
return self.string
These are my tests:
import unittest
from mock import patch
from module.a import A
class MCVETests(unittest.TestCase):
#patch('module.a.B')
def testAcallBwithMockPassCorrect(self, b1):
a = A()
b1.assert_called_once_with("hi")
a.call_b_hello()
a.b.hello.assert_called_once()
#patch('module.a.B')
def testAcallBwithMockPassCorrectWith(self, b1):
a = A()
b1.assert_called_once_with("hi")
a.call_b_hello()
a.b.hello.assert_called_once_with()
#patch('module.a.B')
def testAcallBwithMockFailCorrectWith(self, b1):
a = A()
b1.assert_called_once_with("hi")
a.b.hello.assert_called_once_with()
#patch('module.a.B')
def testAcallBwithMockPassWrong(self, b1):
a = A()
b1.assert_called_once_with("hi")
a.b.hello.assert_called_once()
if __name__ == '__main__':
unittest.main()
My problem as stated in the name of the function is:
Test 1 passes correctly
Test 2 passes correctly
Test 3 fails correctly (I've removed the call to b)
Test 4 passes I am not sure why.
Am I doing something wrong? I am not sure but reading the documentation docs python:
assert_called_once(*args, **kwargs)
Assert that the mock was called exactly once.
This is old, but for others landing here...
For python < 3.6, assert_called_once isn't a thing and so you're actually making a mocked function call which doesn't error
Please see: http://engineroom.trackmaven.com/blog/mocking-mistakes/
You can check the call count instead.

Python: isinstance() undefined global name

I'm new with Python and I'm trying to use classes to program using objects as I do with C++.
I wrote 3 .py files.
a.py
from b import *
class A:
def __init__(self):
self.m_tName = "A"
def test(self):
tB = B()
tB.do( self )
b.py
from a import *
class B:
def __init__(self):
self.m_tName = "B"
def do(self, tA ):
if not isinstance( tA, A ):
print ( "invalid parameter" )
print( "OK" )
demo.py:
from a import *
if __name__ == "__main__":
tA = A()
tA.test()
As you can see I want to use a A() object to call the member function test() that creates a B() object and call the member function do() that uses a A() object.
So in B::do() I want to check the parameters using the built-in function isinstance(). But python tells me that there's a NameError: global name 'A' is not defined.
The A() class file is imported at the top of the b.py file.
Does anyone know what I'm doing wrong here ?
As pointed in some comment, circular dependencies are not well handled if imported in the form from a import A.
In short, the problem with ... import * is that is causes the local scope to have all its declarations overridden, in effect making the identification of from which module (in your case) a class comes from. This causes exactly what you are facing.
Changing the import statement in the following way, together with a classified reference to a.A, produces OK as output.
import a
class B:
def __init__(self):
self.m_tName = "B"
def do(self, tA ):
print tA
if not isinstance( tA, a.A ):
print ( "invalid parameter" )
print( "OK" )
As a bit of additional information, this has already been discussed in Why is "import *" bad?. I would point in special to this answer: https://stackoverflow.com/a/2454460/1540197.
**Edit:**This article explain the import confusion.
You have a circular dependancy, a.py and b.py import each other.
You could move either import statement inside the method where it is used.
So b.py would become:
class A:
def __init__(self):
self.m_tName = "A"
def test(self):
from b import B
tB = B()
tB.do( self )

Categories