I'm doing the 100 days of Python videos and I'm trying to reproduce the pytest example form https://github.com/talkpython/100daysofcode-with-python-course/tree/master/days/10-12-pytest/guess to make sure I understood it. But it doesn't work, even though I think I copied it 1-to-1 (when I run the code from the github, it works, for some reason).
The 3 files below are my reproduction - but the test fails unfortunately. Where is my error?
#scratch_module.py
def dumbfunc():
return "I'm just a simple function."
and
#scratch.py
from scratch_module import dumbfunc
def myfunc():
str = dumbfunc()
return str + " HI, I'M MYFUNC!!!"
print(myfunc())
and
#test_scratch.py
from unittest.mock import patch
import scratch_module
import pytest
from scratch import myfunc
#patch.object(scratch_module, 'dumbfunc')
def test_myfunc(mock_method):
mock_method.return_value = "MOCKED OUT! OK!"
obs = myfunc()
exp = "MOCKED OUT! OK! HI, I'M MYFUNC!!!"
assert obs == exp
Related
In my unittest, I have 2 prompts in the test. I am trying to use 2 #patch("builtins.input"), but it seems to only take the 1 of the return values.
#patch("builtins.input")
#patch("builtins.input")
def test_setProfileName_modify_init_prompt_empty(self, paramName1, paramName2):
paramName1.return_value = self.profileName_prod
paramName2.return_value = self.profileName_dev
a = c.ALMConfig(self.configType)
self.assertTrue(a.setProfileName())
self.assertEqual(a.getProfileName(), self.profileName_dev)
self.assertEqual(a.profileName, self.profileName_dev)
self.assertTrue(a.setProfileName())
self.assertEqual(a.getProfileName(), self.profileName_prod)
self.assertEqual(a.profileName, self.profileName_prod)
The call a.setProfileName() will prompt for 1 input using input() call in my function. In this test, it will call a.setProfileName() twice.
First time I call a.setProfileName(), I would enter the value of self.profileName_prod.
The second time I call it, I would enter the value of self.profileName_dev.
But the test fails after the second a.setProfileName() case (at the second to last assertEqual after the second a.setProfileName() call).
self.assertEqual(a.getProfileName(), self.profileName_prod)
The reason for the failure is because a.getProfileName is returning the value for self.profileName_dev instead of self.profileName_prod.
I had tested my code in the python cli to make sure the behavior is correct.
Any feedback is appreciated.
Thanks guys!
Patching the same function twice does not make it return different values on different calls. You can use the side_effect attribute of the Mock object by setting it with a list of values you want the function to return in successive calls instead:
from unittest.mock import patch
#patch('builtins.input', side_effect=['dev', 'prod'])
def test_input(mock_input):
assert input() == 'dev'
assert input() == 'prod'
test_input() # this will not raise an exception since all assertions are True
I revisited blhsing's solution, and it is more much elegant. Here is my working test code now:
#patch('builtins.input', side_effect=['dev', 'production'])
def test_setProfileName_modify_init_prompt_update_new(self, paramName):
a = c.ALMConfig(self.configType)
self.assertTrue(a.setProfileName())
self.assertEqual(a.getProfileName(), self.profileName_dev)
self.assertEqual(a.profileName, self.profileName_dev)
self.assertEqual(a.getProfileName(), self.profileName_dev)
self.assertTrue(a.setProfileName())
self.assertEqual(a.getProfileName(), self.profileName_prod)
self.assertEqual(a.profileName, self.profileName_prod)
Thanks everyone for your comments! :)
To provide a more simple and to the point answer for anyone visiting this in 2020 and later, you can just do
`with patch("builtins.input", return_value = "Whatever you want returned"):
self.assertEqual("<Object>", "Whatever you want returned")
`
in python 3.8 in later.
To see a full easy to follow example keep reading otherwise stop here.
Full Example:
The below code shows a full example of this with a class named "AnsweredQuestion" and with a unit test
`class AnsweredQuestion:
def __init__(self):
print("I hope you find this example helpful")
def get_input(self):
print("Enter your input")
entered_data = input()
print("You entered '" + entered_data + "'")
return get_input
`
Unit Test to test above class AnsweredQuestion
`
import builtins
import unittest
import sys
sys.path.append(".")
# Assuming a directory named "answers" in your setup
import answers
from answers import AnsweredQuestion
from unittest.mock import Mock, patch
class TestAnsweredQuestion(unittest.TestCase):
def test_get_input(self):
with patch("builtins.input", return_value = "Thanks. This is correct"):
self.assertEqual(AnsweredQuestion.get_input(), "Thanks this is correct")
if __name__ == '__main__':
unittest.main()
`
I have been reading into python mocking but can't get my head around why the following code is failing.
I have two classes, a Potato and a PotatoBag like the following. Figure is stored in food.py and Report is stored in bag.py.
class Potato:
def create_potato(self):
pass
def output_potato(self):
pass
class PotatoBag:
def __init__(self, potatoes):
self.potatoes = potatoes
def output_to_file(self):
for fig in self.potatoes:
fig.create_potato()
fig.output_potato()
Currently I am trying to unit test the output method so that Report correctly calls create_figure and output_figure from Figure using a mock. This is my test code:
from unittest.mock import MagicMock, patch
from bag import PotatoBag
from food import Potato
import pytest
#pytest.fixture(scope='module')
def potatoes():
x = Potato()
y = Potato()
return [x, y]
#patch('food.Potato')
def test_output_to_file(mock_potato, potatoes):
test_potato_bag = PotatoBag(potatoes)
test_potato_bag.output_to_file()
mock_potato.return_value.create_potato.assert_called()
mock_potato.return_value.output_potato.assert_called()
Immediately pytest yields an AssertionError stating that create_figure was never called.
_mock_self = <MagicMock name='Potato().create_potato' id='140480853451272'>
def assert_called(_mock_self):
"""assert that the mock was called at least once
"""
self = _mock_self
if self.call_count == 0:
msg = ("Expected '%s' to have been called." %
self._mock_name or 'mock')
> raise AssertionError(msg)
E AssertionError: Expected 'create_potato' to have been called.
/home/anaconda3/lib/python3.7/unittest/mock.py:792: AssertionError
What is wrong with my code?
You are passing the Report a list of Figures from your fixture instead of a mock.
Changing your test to...
#patch('figure.Figure')
def test_output_to_file(mock_figure, figures):
test_report = Report([mock_figure])
test_report.output_to_file()
mock_figure.create_figure.assert_called_once()
mock_figure.output_figure.assert_called_once()
This resolves testing that output_to_file correctly is calling the functions on Figure without actually worrying about setting up a figure and dealing with any side effects or additional complexities that may come with calling those functions. The worries of that can be saved for the unit tests for Figure ;)
I want to add unit testing to the assessment of my high school programming class.
If I have twenty submissions of files that look like this:
def calculateReturn(principle, rate, freq, time):
final = principle * (1 + (rate/freq)) ** (freq * time)
return final
Can I use a test case like this?
import unittest
class test(unittest.TestCase):
def test1(self):
value = calculateReturn(5000, 0.05, 12, 11)
self.assertAlmostEqual(value, 8235.05, 2)
if __name__ == '__main__':
unittest.main()
How do I run this one simple test on twenty modules?
FURTHER INFORMATION
For testing I have created three "submissions" all of which show different ways of calculating x^y.
submission1.py:
from math import pow
def powerFunction(base, power):
result = pow(base, power)
return result
submission2.py:
def powerFunction(base, power):
result = base ** power
return result
submission3.py:
def powerFunction(base, power):
result = 1
for i in range(power):
result = result * base
return result
The test code is:
import unittest
import importlib
class MyTest(unittest.TestCase):
def setUp(self):
pass
def test_power_3_4(self):
self.assertEqual(module.powerFunction(2, 3), 8)
files = ['submission1', 'submission2', 'submission3']
for file in files:
module = importlib.import_module(file)
print module
unittest.main()
if the test code is run the console output shows only submission1 being tested:
/System/Library/Frameworks/Python.framework/Versions/2.7/bin/python2.7
/Users/staff/PycharmProjects/UnitTest/powerTest.py
<module 'submission1' from '/Users/staff/PycharmProjects/UnitTest/
submission1.pyc'>
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Process finished with exit code 0
Interestingly if I don't use unit testing I can correctly import and test using this approach:
import importlib
files = ['submission1', 'submission2', 'submission3']
for file in files:
module = importlib.import_module(file)
print module
print module.powerFunction(2,3)
The console output here is:
/System/Library/Frameworks/Python.framework/Versions/2.7/bin/
python2.7 /Users/staff/PycharmProjects/UnitTest/importlib1.py
<module 'submission1' from '/Users/staff/PycharmProjects/UnitTest/
submission1.pyc'>
8.0
<module 'submission2' from '/Users/staff/PycharmProjects/UnitTest/
submission2.pyc'>
8
<module 'submission3' from '/Users/staff/PycharmProjects/UnitTest/
submission3.pyc'>
8
Process finished with exit code 0
It may well be that the unittest module is not the best approach here but I'm still interested on how to implement it.
You can use importlib to load Python modules from specific files, then run the test cases on each one.
glob may be helpful to create the list of files.
Given that this has been active for a month with no answers I have come the the realisation that it is because I'm asking for the wrong thing.
From what I can gather, unittest is for running a suite of tests on a single application. It is not designed to run a single test on a suite of applications.
John's suggestion to investigate importlib helped set me on the path to success. Thanks John.
The code posted in the original post update seems to be the most appropriate solution to my problem.
I don't know why I'm just not getting this, but I want to use mock in Python to test that my functions are calling functions in ftplib.FTP correctly. I've simplified everything down and still am not wrapping my head around how it works. Here is a simple example:
import unittest
import ftplib
from unittest.mock import patch
def download_file(hostname, file_path, file_name):
ftp = ftplib.FTP(hostname)
ftp.login()
ftp.cwd(file_path)
class TestDownloader(unittest.TestCase):
#patch('ftplib.FTP')
def test_download_file(self, mock_ftp):
download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
mock_ftp.cwd.assert_called_with('pub/files')
When I run this, I get:
AssertionError: Expected call: cwd('pub/files')
Not called
I know it must be using the mock object since that is a fake server name, and when run without patching, it throws a "socket.gaierror" exception.
How do I get the actual object the fuction is running? The long term goal is not having the "download_file" function in the same file, but calling it from a separate module file.
When you do patch(ftplib.FTP) you are patching FTP constructor. dowload_file() use it to build ftp object so your ftp object on which you call login() and cmd() will be mock_ftp.return_value instead of mock_ftp.
Your test code should be follow:
class TestDownloader(unittest.TestCase):
#patch('ftplib.FTP', autospec=True)
def test_download_file(self, mock_ftp_constructor):
mock_ftp = mock_ftp_constructor.return_value
download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
mock_ftp_constructor.assert_called_with('ftp.server.local')
self.assertTrue(mock_ftp.login.called)
mock_ftp.cwd.assert_called_with('pub/files')
I added all checks and autospec=True just because is a good practice
Like Ibrohim's answer, I prefer pytest with mocker.
I have went a bit further and have actually wrote a library which helps me to mock easily. Here is how to use it for your case.
You start by having your code and a basic pytest function, with the addition of my helper library to generate mocks to modules and the matching asserts generation:
import ftplib
from mock_autogen.pytest_mocker import PytestMocker
def download_file(hostname, file_path, file_name):
ftp = ftplib.FTP(hostname)
ftp.login()
ftp.cwd(file_path)
def test_download_file(mocker):
import sys
print(PytestMocker(mocked=sys.modules[__name__],
name=__name__).mock_modules().prepare_asserts_calls().generate())
download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
When you run the test for the first time, it would fail due to unknown DNS, but the print statement which wraps my library would give us this valuable input:
...
mock_ftplib = mocker.MagicMock(name='ftplib')
mocker.patch('test_29817963.ftplib', new=mock_ftplib)
...
import mock_autogen
...
print(mock_autogen.generator.generate_asserts(mock_ftplib, name='mock_ftplib'))
I'm placing this in the test and would run it again:
def test_download_file(mocker):
mock_ftplib = mocker.MagicMock(name='ftplib')
mocker.patch('test_29817963.ftplib', new=mock_ftplib)
download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
import mock_autogen
print(mock_autogen.generator.generate_asserts(mock_ftplib, name='mock_ftplib'))
This time the test succeeds and I only need to collect the result of the second print to get the proper asserts:
def test_download_file(mocker):
mock_ftplib = mocker.MagicMock(name='ftplib')
mocker.patch(__name__ + '.ftplib', new=mock_ftplib)
download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
mock_ftplib.FTP.assert_called_once_with('ftp.server.local')
mock_ftplib.FTP.return_value.login.assert_called_once_with()
mock_ftplib.FTP.return_value.cwd.assert_called_once_with('pub/files')
If you would like to keep using unittest while using my library, I'm accepting pull requests.
I suggest using pytest and pytest-mock.
from pytest_mock import mocker
def test_download_file(mocker):
ftp_constructor_mock = mocker.patch('ftplib.FTP')
ftp_mock = ftp_constructor_mock.return_value
download_file('ftp.server.local', 'pub/files', 'wanted_file.txt')
ftp_constructor_mock.assert_called_with('ftp.server.local')
assert ftp_mock.login.called
ftp_mock.cwd.assert_called_with('pub/files')
Omnicompletion for Python seems to fail when there is a "from" import instead of a normal one.
For example, if I have these two files:
Test.py:
class Test:
def method(self):
pass
main.py:
from Test import Test
class Test2:
def __init__(self):
self.x = Test()
If I try to activate omnicompletion for self.x... it says "Pattern not found".
However, if I change the import statement to:
import Test
and the self.x declaration to:
self.x = Test.Test()
then I'm able to use omnicompletion as expected (it suggests "method", for example).
I'm using Vim 7.2.245 and the default plugin for Python code completion (pythoncomplete).
Should I set some variable? Or is this behavior expected?
Update:
Based on Jared's answer, I found out something by accident:
Omnicompletion doesn't work on this:
from StringIO import StringIO
class Test:
def __init__(self):
self.x = StringIO()
self.x.<C-x><C-o>
s = Test()
But works on this:
from StringIO import StringIO
class Test:
def __init__(self):
self.x = StringIO()
self.x.<C-x><C-o>
s = Test()
s.x = StringIO()
The only difference is the redeclaration of x (actually, it also works if I remove the declaration inside __init__).
I tested my example again, and I think the problem is not the "from" import, but the use of the imported class inside another class.
If I change the file main.py to:
from Test import Test
class Test2:
def __init__(self):
self.x = Test()
self.x.<C-x><C-o>
y = Test()
y.<C-x><C-o>
The first attempt to use omnicompletion fails, but the second works fine.
So yep, looks like a bug in the plugin :)
update: ooh, so I checked your example, and I get completion for
x = Test()
x.<C-x><C-o>
but not
o = object()
o.x = Test()
o.x.<C-x><C-o>
...I'm gonna do some digging
update 2: revenge of Dr. Strangelove
and...this is where it get's weird.
from StringIO import StringIO
class M:
pass
s = M()
s.x = StringIO()
s.x.<C-x><C-o>
completes. but this
from StringIO import StringIO
class M: pass
s = M()
s.x = StringIO()
s.x.<C-x><C-o>
Did you catch the difference? nothing syntactically -- just a little whitespace
And yet it breaks completion. So there's definitely a parsing bug in there somewhere (why they don't just use the ast module, I have no idea...)
[end of updates]
On first blush, I can't reproduce your problem; here's my test file:
from os import path
path.<C-x><C-o>
and I get completion. Now, I know it's not exactly your situation, but it shows that pythoncomplete knows about 'from'.
And now the more in-depth example:
from StringIO import StringIO
s = StringIO()
s.<C-x><C-o>
And...completion! Could you try that example to see if it works with builtin modules for you? If that's the case, you should probably check paths...
If it still doesnt work, and you're up for some digging around, check out line #555 of pythoncomplete.vim [at /usr/share/vim/vim72/autoload/pythoncomplete.vim on my ubuntu machine]:
elif token == 'from':
mod, token = self._parsedotname()
if not mod or token != "import":
print "from: syntax error..."
continue
names = self._parseimportlist()
for name, alias in names:
loc = "from %s import %s" % (mod,name)
if len(alias) > 0: loc += " as %s" % alias
self.scope.local(loc)
freshscope = False
as you can see, this is where it handles from statements.
Cheers