I have a class, which use a class variable to choose which logic to execute.
#in file1:
class SomeHelper():
def __init__(self):
self.my_var = 0
#in file2:
import file1
class MyClass():
...
...
def calculate():
inst = file1.SomeHelper()
if x > inst.my_var:
etc etc
I am writing a unit test and mocking SomeHelper() in another file:
from file 2 import MyClass
# tried both
#patch('file2.file1') OR #patch('file2.file1.SomeHelper')
def test_calculate(self, mock_helper):
mock_helper.my_var = 0
to_test = MyClass.calculate()
And I get the following error:
TypeError: '>' not supported between instances of 'MagicMock' and 'int'.
I thought I defined my_var after I patched the module.
Here is the unit test solution for Python 3.7.5:
file1.py:
class SomeHelper():
def __init__(self):
self.my_var = 0
file2.py:
import file1
class MyClass():
#classmethod
def calculate(cls):
x = 1
inst = file1.SomeHelper()
if x > inst.my_var:
return True
return False
test_file2.py:
import unittest
from unittest.mock import patch
from file2 import MyClass
class TestMyClass(unittest.TestCase):
#patch('file2.file1')
def test_calculate(self, mock_file1):
inst = mock_file1.SomeHelper.return_value
inst.my_var = 0.5
to_test = MyClass.calculate()
self.assertTrue(to_test)
mock_file1.SomeHelper.assert_called_once()
#patch('file2.file1')
def test_calculate_2(self, mock_file1):
inst = mock_file1.SomeHelper.return_value
inst.my_var = 2
to_test = MyClass.calculate()
self.assertFalse(to_test)
mock_file1.SomeHelper.assert_called_once()
if __name__ == '__main__':
unittest.main()
Unit test result with coverage report:
..
----------------------------------------------------------------------
Ran 2 tests in 0.002s
OK
Name Stmts Miss Cover Missing
------------------------------------------------------------------------
src/stackoverflow/50242955/file1.py 3 1 67% 3
src/stackoverflow/50242955/file2.py 8 0 100%
src/stackoverflow/50242955/test_file2.py 16 0 100%
------------------------------------------------------------------------
TOTAL 27 1 96%
Source code: https://github.com/mrdulin/python-codelab/tree/master/src/stackoverflow/50242955
Related
I have the following function in the file myfile.py:
#myfile.py
import psutil
class RunnableObject:
def run(self):
parent = psutil.Process()
print(parent)
children = parent.children(recursive=True)
print(children)
Then I have a unit test where runnable_object is an instance of the RunnableObject class which I setup using a pytest fixture.
#patch("myfile.psutil")
def test_run_post_request(self, psutil_, runnable_object):
runnable_object.run()
assert psutil_.Process.call_count == 1
assert psutil_.Process.children.call_count == 1
When I run my test however I get the following error:
assert psutil_.Process.call_count == 1
> assert psutil_.Process.children.call_count == 1
E assert 0 == 1
E +0
E -1
-1
tests/unit/test_experiment.py:1651: AssertionError
My stdout:
<MagicMock name='psutil.Process()' id='3001903696'>
<MagicMock name='psutil.Process().children()' id='3000968624'>
I also tried to use #patch.object(psutil.Process, "children") as well as#patch("myfile.psutil.Process") and #patch("myfile.psutil.Process.children") but that gave me the same problem.
children is the property of the return value of psutil.Process(). NOT the property of the Process method.
So the correct assertion is:
test_myfile.py:
from unittest import TestCase
import unittest
from unittest.mock import patch
from myfile import RunnableObject
class TestRunnableObject(TestCase):
#patch("myfile.psutil")
def test_run_post_request(self, psutil_):
runnable_object = RunnableObject()
runnable_object.run()
assert psutil_.Process.call_count == 1
assert psutil_.Process().children.call_count == 1
if __name__ == '__main__':
unittest.main()
test result:
<MagicMock name='psutil.Process()' id='4394128192'>
<MagicMock name='psutil.Process().children()' id='4394180912'>
.
----------------------------------------------------------------------
Ran 1 test in 0.002s
OK
Name Stmts Miss Cover Missing
-------------------------------------------------------------------------
src/stackoverflow/67362647/myfile.py 7 0 100%
src/stackoverflow/67362647/test_myfile.py 13 0 100%
-------------------------------------------------------------------------
TOTAL 20 0 100%
I'm having some issue while creating unittest for internal parameter.
My structure is:
[1] my_animal.py contains Myclass and method: do_bite()
my_animal.py
class Myclass():
def do_bite(self):
return 1
[2] my_module.py contains jobMain("") which is using the method from my_animal.py
my_module.py
import sys
from someclass import Myclass
def jobMain(directoryPath):
flag = -1
result = Myclass()
if result.do_bite() is None:
flag = 0
if result.do_bite() is 1:
flag = 1
if result.do_bite() is 2:
flag = 2
[3] my_test.py contains the unittest to test jobMain in my_module.py
my_test.py
# Mock Myclass.dobite to None
#pytest.fixture
def mock_dobite0():
with mock.patch('my_module.Myclass') as mocked_animal:
mocked_animal.return_value.do_bite.return_value = None
yield
# Mock Myclass.dobite to 1
#pytest.fixture
def mock_dobite1():
with mock.patch('my_module.Myclass') as mocked_animal:
mocked_animal.return_value.do_bite.return_value = 1
yield
# Mock Myclass.dobite to 2
#pytest.fixture
def mock_dobite2():
with mock.patch('my_module.Myclass') as mocked_animal:
mocked_animal.return_value.do_bite.return_value = 2
yield
# My unittest to test dobite() method
def test_dobite0(mock_Myclass, mock_dobite0):
jobMain("")
def test_dobite1(mock_Myclass, mock_dobite1):
jobMain("")
def test_dobite2(mock_Myclass, mock_dobite2):
jobMain("")
My question is: How to test 'flag' parameter inside JobMain?
'flag' para must be assigned the correct value.( eg: dobite = 1 => flag = 1)
The variable para only exists in the scope of jobMain. If you want to use the variable outside jobMain the most common ways are
1) return the value
This is quite obvious. Since jobMain is a function, it returns a value. Without an explicit return statement you return None. You could just
def jobmain(pth):
# do stuff and assign flag
return flag
# and inside tests
assert jobmain("") == 1
2) Use a class instead
If you want the jobMain to remember some state, then it is common practice to use objects. Then flag would be attribute of the object and could be accessed from outside, after you call any method (function) of JobMain. For example
class JobMain:
def __init__(self):
self.flag = -1
def run(self, pth):
result = Myclass()
if result.do_bite() is None:
self.flag = 0
if result.do_bite() is 1:
self.flag = 1
if result.do_bite() is 2:
self.flag = 2
# and inside test
job = JobMain()
job.run()
assert job.flag == 1
Note
I just copy-pasted your code for setting the flag. Note that you call do_bite() many times, if the resulting value is None or 1. Also, when testing against a number, one should use == instead of is.
How to test 'flag' parameter inside JobMain?
You don't. It's an internal variable. Testing it would be glass-box testing; the test will break if the implementation changes.
Instead, test the effect of flag. This is black-box testing. Only the interface is tested. If the implementation changes the test still works allowing the code to be aggressively refactored.
Note: If you don't hard code result = Myclass() you don't need to mock. Pass it in as an argument with the default being Myclass().
def jobMain(directoryPath, result=Myclass()):
Then you don't need to patch Myclass(). Instead, pass in a mock object.
# I don't know unittest.mock very well, but something like this.
mock = Mock(Myclass)
mock.do_bite.return_value = 2
jobMain('', result=mock)
This also makes the code more flexible outside of testing.
I have some python code and I want to coverage it with unittest.
My simple code is:
import os
import glob
class GitRepoParser:
def __init__(self, repo_dir: str):
self.repo_dir = repo_dir
def get_folder_path(self, service_name: str) -> str:
dir_path = '{path}/configuration/settings/{name}'.format(
path=self.repo_dir,
name=service_name
)
return dir_path
def create_new_service(self, service_name: str, service_version: str):
dir_path = self.get_folder_path(service_name)
if os.path.isdir(dir_path):
return
os.makedirs(dir_path)
property_file = '{folder}/{name}-deployment.properties'.format(
folder=dir_path,
name=service_name
)
current_path = os.path.dirname(__file__)
template_path = os.path.join(
current_path, '..', 'file_templates/deployment.properties'
)
if not os.path.isfile(template_path):
return
template_file = open(template_path, 'r')
with open(property_file, 'w') as properties:
for line in template_file:
# Create parser for this code part
value = line.strip().replace(
'%service_name%', service_name
)
properties.write(value + '\n')
template_file.close()
Help me to create some unittest for this lines of code. I create some class GitRepoParserTest in my python tests:
class GitRepoParserTest(unittest.TestCase):
def setUp(self):
self.app = create_app('testing')
self.app_context = self.app.app_context()
self.app_context.push()
self.parser = GitRepoParser('/path-to-download')
def test_get_folder_path(self):
folder_path = self.parser.get_folder_path(service_name='oleg-service')
self.assertIsInstance(folder_path, str)
def test_create_new_service(self):
pass
But I don't understand how I need to write tests for create_new_service function.
Please help me.
Thank you.
Here is the unit test solution for Python 3.7.5:
test_GitRepoParser.py:
import unittest
from unittest.mock import Mock, patch, mock_open, call
from GitRepoParser import GitRepoParser, os
class GitRepoParserTest(unittest.TestCase):
def setUp(self):
self.parser = GitRepoParser('/path-to-download')
self.mock_open = mock_open(read_data='some data')
def test_get_folder_path(self):
folder_path = self.parser.get_folder_path(service_name='oleg-service')
self.assertIsInstance(folder_path, str)
#patch('GitRepoParser.os')
def test_create_new_service_read_and_write_file_correctly(self, mock_os):
mock_path = Mock()
mock_path.dirname.return_value = '/Users/foo'
mock_path.isdir.return_value = False
mock_path.join.return_value = '/Users/whatever'
mock_path.isfile.return_value = True
mock_os.path = mock_path
mock_os.makedirs = Mock()
with patch('GitRepoParser.open', self.mock_open):
self.parser.create_new_service(service_name='oleg-service', service_version='1.0')
mock_path.isdir.assert_called_with('/path-to-download/configuration/settings/oleg-service')
mock_os.makedirs.assert_called_with('/path-to-download/configuration/settings/oleg-service')
expected = [call('/Users/whatever', 'r'),
call('/path-to-download/configuration/settings/oleg-service/oleg-service-deployment.properties', 'w')]
self.mock_open.assert_has_calls(expected)
handle = self.mock_open()
handle.write.assert_called()
handle.close.assert_called_once()
#patch('GitRepoParser.os')
def test_create_new_service_do_nothing_if_dir_path_is_dir(self, mock_os):
mock_path = Mock()
mock_path.isdir.return_value = True
mock_os.path = mock_path
mock_os.makedirs = Mock()
self.parser.create_new_service(service_name='oleg-service', service_version='1.0')
mock_path.isdir.assert_called_with('/path-to-download/configuration/settings/oleg-service')
mock_os.makedirs.assert_not_called()
if __name__ == '__main__':
unittest.main()
Unit test result with coverage report:
...
----------------------------------------------------------------------
Ran 3 tests in 0.023s
OK
Name Stmts Miss Cover Missing
--------------------------------------------------------------------------------
src/stackoverflow/58384586/GitRepoParser.py 24 1 96% 35
src/stackoverflow/58384586/test_GitRepoParser.py 37 0 100%
--------------------------------------------------------------------------------
TOTAL 61 1 98%
As you can see, there is still a if condition branch left. You can finish it by yourself.
Source code: https://github.com/mrdulin/python-codelab/tree/master/src/stackoverflow/58384586
I am planning to use pytest and pytest-mock for validating the Python code. Being a newbie, wrote a sample code to validate the mock on class and seeing failure. I am wondering what went wrong.
src/main.py
class Main(object):
def __init__(self, my_var=None):
self.var = my_var
def internal_func(self, var=10):
my_var = var + 20
return my_var
def test_func(self):
val = self.internal_func(20)
return val + 40
tests/test_main.py
import pytest
from pytest_mock import mocker
from src.main import Main
def new_func(cls, *args, **kwargs):
return 2
def test_main_mock(mocker):
mocker.patch.object(Main, 'internal_func')
val = Main().test_func()
assert Main.internal_func.assert_called_with(20)
It fails with the following error
======================================================================================== FAILURES ========================================================================================
_____________________________________________________________________________________ test_main_mock _____________________________________________________________________________________
mocker = <pytest_mock.MockFixture object at 0x7f34f490d8d0>
def test_main_mock(mocker):
mocker.patch.object(Main, 'internal_func')
main = Main()
val = main.test_func()
# assert val == 80
> assert Main.internal_func.assert_called_with(20)
E AssertionError: assert None
E + where None = <bound method MagicMock.wrap_assert_called_with of <MagicMock name='internal_func' id='139865418160784'>>(20)
E + where <bound method MagicMock.wrap_assert_called_with of <MagicMock name='internal_func' id='139865418160784'>> = <MagicMock name='internal_func' id='139865418160784'>.assert_called_with
E + where <MagicMock name='internal_func' id='139865418160784'> = Main.internal_func
tests/test_main.py:13: AssertionError
The return_value or side_effect must be set before the patched func take effect
def test_main_mock(mocker):
# mock internal_func of class Main
mocked_internal_func = mocker.patch.object(Main, 'internal_func')
# assign return_value or side_effect
mocked_internal_func.return_value = -10
# class instance
ma = Main()
val = ma.test_func()
assert ma.internal_func.assert_called_with(20)
Correction of mistake, the assert should not be used together with assert_called_with, they are independent assert.
assert val == 30
mocked_internal_func.assert_called
ma.internal_func.assert_called_with(20)
mocked_internal_func.assert_called_with(20)
In Ruby 1.9, I can use its class variable like the following:
class Sample
##count = 0
def initialize
##count += 1
end
def count
##count
end
end
sample = Sample.new
puts sample.count # Output: 1
sample2 = Sample.new
puts sample2.count # Output: 2
How can I achieve the above in Python 2.5+ ?
class Sample(object):
_count = 0
def __init__(self):
Sample._count += 1
#property
def count(self):
return Sample._count
The use is a bit different from Ruby; e.g. if you have this code in module a.py,
>>> import a
>>> x = a.Sample()
>>> print x.count
1
>>> y = a.Sample()
>>> print x.count
2
having a Sample.count "class property" (with the same name as the instance property) would be a bit tricky in Python (feasible, but not worth the bother IMHO).