Pytest: Test a method with different global variable - python

I am writing pytest for a script where the method is using the global variable. While writing a test method for the script, I want to pass the DUMMY global variable(i.e. 2) to it instead of the original value(i.e. 55). So I wrote a script (can see below), but that resulted my failure.
# script1.py
VAL = 55
def add_one():
return VAL+1
Pytest script for the above script:
# test_script1.py
import pytest
from script1 import add_one
DUMMY_VAL = 2
#pytest.mark.parametrize("VAL", DUMMY_VAL)
def test_add_one():
expected_output = 3
observed_output = add_one()
assert observed_output == expected_output
But the above test script is Failing as it is taking the VAL to be 55 instead of 2. So my question is, is there a way by which I can pass DUMMY_VAL to the test method so as to pass my test case.
Also, one condition: I do not wish to change my method definition in the script.py file.

The trouble you are having are all signs telling you not to use globals. Especially since you are using the language of function parameters (i.e. s there a way by which I can pass DUMMY_VAL).
Having said that, you can patch the variable with something like — you just need to do the imports in a way that you can access the imported module:
from unittest.mock import patch
import script1
DUMMY_VAL = 2
#patch('script1.VAL', DUMMY_VAL)
def test_add_one():
expected_output = 3
observed_output = script1.add_one()
assert observed_output == expected_output

Related

Unit test function without return value

I have one function like this one:
def function(df, path_write):
df['A'] = df['col1'] * df['col2']
write(df, path)
The function is not that simple but the question is, how can i make a unit test if the function do not return any value??
If the function returns the new df it's simple, just make:
assert_frame_equal from the library from pandas.testing import assert_frame_equal and mock the write method.
But without that return, how can i test the df line??
In general, I can think of only two kinds of functions: Those that return a value and those that produce side-effects.
With the second kind, you typically don't want the side-effects to actually happen during testing. For example, if your function writes something to disk or sends some data to some API on the internet, you usually don't want it to actually do that during the test, you only want to ensure that it attempts to do the right thing.
To roll with the example of disk I/O as a side-effect: You would usually have some function that does the actual writing to the filesystem that the function under testing calls. Let's say it is named write. The typical apporach would be to mock that write function in your test. Then you would need to verify that that mocked write was called with the arguments you expected.
Say you have the following code.py for example:
def write(thing: object, path: str) -> None:
print("Some side effect like disk I/O...")
def function(thing: object, file_name: str) -> None:
...
directory = "/tmp/"
write(thing, path=directory + file_name)
To test function, I would suggest the following test.py:
from unittest import TestCase
from unittest.mock import MagicMock, patch
from . import code
class MyTestCase(TestCase):
#patch.object(code, "write")
def test_function(self, mock_write: MagicMock) -> None:
test_thing = object()
test_file_name = "test.txt"
self.assertIsNone(code.function(test_thing, test_file_name))
mock_write.assert_called_once_with(
test_thing,
path="/tmp/" + test_file_name,
)
Check out unittest.mock for more details on mocking with the standard library. I would strongly advise to use the tools there and not do custom monkey-patching. The latter is certainly possible, but always carries the risk that you forget to revert the patched objects back to their original state after every test. That can break the entire rest of your test cases and depending on how you monkey-patched, the source of the resulting errors may become very hard to track down.
Hope this helps.
In the mock for write() you can add assert statements to ensure the form of df is as you would expect. For example:
def _mock_write(df, path):
assert path == '<expected path value>'
assert_frame_equal(df, <expected dataframe>)
So the full test case would be:
def test_function(self, monkeypatch):
# Define mock function
def _mock_write(df, path):
assert path == '<expected path value>'
assert_frame_equal(df, <expected dataframe>)
# Mock write function
monkepyatch.setattr(<MyClass>, 'write', _mock_write)
# Run function to enter mocked write function
function(test_df, test_path_write)
N.B. This is assuming you are using pytest as your test runner which supports the set up and tear down of monkeypatch. Other answers show the usage for the standard unittest framework.

Testing whether a variable outside the current module is written to

I'm trying to write a pytest suite for functions that write to variables in a dedicated global_variables module, and I can't work out how to check if the variables are getting written there. I've tried using pytest-mock's mocker.patch to mock the variables, but mocker seems to just allow you to read in from a mocked variable, not write to one.
The code being tested:
import global_variables as gv
def main(filename):
gv.name = filename
do_other_stuff()
My unit test:
import pytest
def test_main_writes_to_gv():
# arrange
output = mocker.patch(gv.name)
# act
app.main('foo')
# assert
assert output == 'foo'
I want to add some sort of listener to see if gv.name is being written to, similar to the way that mocker's .assert_called_with() method works, but I'm looking for something like .assert_written_to.

How do I have to mock patch in this use case?

The initial scenerio is writing tests for functions from a library (lib.py).
lib.py:
def fun_x(val):
# does something with val
return result
def fun(val):
x = fun_x(val)
# does seomthing with x
return result
test__lib.py
import pytest
import lib
def lib_fun_x_mocked(val):
return "qrs"
def test_fun():
assert lib.fun("abc") == "xyz"
But lib.fun_x() does something very expensive or requires a resource not reliably available or not determinisitc. So I want to subsitute it with a mock function such that when the test test_fun() is executed lib.fun() uses lib_fun_x_mocked() instead of fun_x() from its local scope.
So far I'm running into cryptic error messages when I try to apply mock/patch recipes.
You can use the built-in fixture monkeypatch provided by pytest.
import lib
def lib_fun_x_mocked(some_val): # still takes an argument
return "qrs"
def test_fun(monkeypatch):
with monkeypatch.context() as mc:
mc.setattr(lib, 'fun_x', lib_fun_x_mocked)
result = lib.fun('abc')
assert result == 'qrs'
Also as a side note, if you are testing the function fun you shouldn't be asserting the output of fun_x within that test. You should be asserting that fun behaves in the way that you expect given a certain value is returned by fun_x.

monkey patch not working properly

So I'm running py.test and trying to use monkeypatch. I understand that monkeypatch's intended purpose is to replace attributes in a module so that they can be tested. And I get that we can substitute in mock functions in order to do this.
Currently I am trying to run essentially the following block of code.
from src.module.submodule import *
def mock_function(parameter = None):
return 0
def test_function_works(monkeypatch):
monkeypatch.setattr("src.module.submodule.function",mock_function ]
assert function(parameter = None) == 0
When the test runs, instead of swapping in mock_function, it just runs function . Could there be a reason why monkeypatch isn't activating
I have got monkey patch running succesfully with other code before. So I don't see why this isn't working.
I haven't used pytest for this stuff, but I know that with the mock library, functions are patched in the namespace where they're called. i.e. from src.module.submodule import * imports src.module.submodule.function into your namespace, but you then patch it in its original namespace, so your local name for the function still accesses the original, unpatched code.
If you change this to
import src.module.submodule
def mock_function(parameter = None):
return 0
def test_function_works(monkeypatch):
monkeypatch.setattr("src.module.submodule.function",mock_function ]
assert src.module.submodule.function(parameter = None) == 0
does it succeed?
Looks like a typo, shouldn't it be
monkeypatch.setattr("src.module.submodule.function",mockIfunction)
i.e. mockIfunction instead of mock_function?

changing order of unit tests in Python

How can I make it so unit tests in Python (using unittest) are run in the order in which they are specified in the file?
You can change the default sorting behavior by setting a custom comparison function. In unittest.py you can find the class variable unittest.TestLoader.sortTestMethodsUsing which is set to the builtin function cmp by default.
For example you can revert the execution order of your tests with doing this:
import unittest
unittest.TestLoader.sortTestMethodsUsing = lambda _, x, y: cmp(y, x)
Clever Naming.
class Test01_Run_Me_First( unittest.TestCase ):
def test010_do_this( self ):
assertTrue( True )
def test020_do_that( self ):
etc.
Is one way to force a specific order.
As said above, normally tests in test cases should be tested in any (i.e. random) order.
However, if you do want to order the tests in the test case, apparently it is not trivial.
Tests (method names) are retrieved from test cases using dir(MyTest), which returns a sorted list of members. You can use a clever (?) hack to order methods by their line numbers. This will work for one test case:
if __name__ == "__main__":
loader = unittest.TestLoader()
ln = lambda f: getattr(MyTestCase, f).im_func.func_code.co_firstlineno
lncmp = lambda a, b: cmp(ln(a), ln(b))
loader.sortTestMethodsUsing = lncmp
unittest.main(testLoader=loader, verbosity=2)
There are also test runners which do that by themselves – I think py.test does it.
Use proboscis library as I mentioned already (please see short description there).
The default order is alphabetical. You can put test_<int> before every test to specify the order of execution.
Also you can set unittest.TestLoader.sortTestMethodsUsing = None to eliminate the sort.
Check out Unittest Documentation for more details.
I found a solution for it using PyTest ordering plugin provided here.
Try py.test YourModuleName.py -vv in CLI and the test will run in the order they have appeared in your module.
I did the same thing and works fine for me.
Note: You need to install PyTest package and import it.
hacky way (run this file in pycharm or other unit test runner)
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import unittest
def make_suite():
class Test(unittest.TestCase):
def test_32(self):
print "32"
def test_23(self):
print "23"
suite = unittest.TestSuite()
suite.addTest(Test('test_32'))
suite.addTest(Test('test_23'))
return suite
suite = make_suite()
class T(unittest.TestCase):
counter = 0
def __call__(self, *args, **kwargs):
res = suite._tests[T.counter](*args, **kwargs)
T.counter += 1
return res
for t in suite._tests:
name = "{}${}".format(t._testMethodName, t.__class__.__name__)
setattr(T, name, t)

Categories

Resources