I have a very large of set tests that use #pytest.mark.parametrize with a fairly large set of custom marks. I can't figure out a way to access those marks from within the test. The documentation explains how to do this from a conftest.py file, but not from the test function.
I don't really need to operate on the marks, all I need is to register them.
pytest.ini:
[pytest]
markers =
MarkFoo
MarkBar
test.py:
import pytest
from typing import Any
from dataclasses import dataclass
#dataclass(frozen=True)
class FooTest:
name: str # Unique test name
param: int # A parameter
marks: Any = () # Marks for this test
test_list = [
FooTest(name='Foo', param=1, marks=(pytest.mark.MarkFoo)),
FooTest(name='Bar', param=2, marks=(pytest.mark.MarkBar)),
]
#pytest.mark.parametrize( "name, param, ",
[ pytest.param(t.name, t.param, marks=t.marks) for t in test_list ]
)
def test_run_foo_bar(name, param, record_property):
# How to read the marks here?
# record_property("marks:", ???)
print(name, param)
How can I access the marks from the test? Thanks!
Turns out it's super simple if you use a fixture:
#pytest.fixture()
def test_marks(request):
marks = ["MarkFoo", "MarkBar"]
return [m for m in request.node.own_markers if m.name in marks]
def test_run_foo_bar(name, param, record_property, test_marks):
record_property("Test Marks:", test_marks)
print(name, param, test_marks)
Related
I have a parameterized test that receives a long string as an argument, and the problem is that when executing pytest it prints the entire string
#pytest.mark.parametrize(
"long_string_p1, long_string_p2",
[
(a,b) for a,b in zip(list_str_1, list_str_2)
]
)
def test_with_long_strings(long_string_p1, long_string_p2):
# Logic
assert long_string_p1 != long_string_p2
pytest
test_example.py::test_with_long_strings[looooooooong string1-loooooooooong string2] PASSED
I would like it to only show the last part of the string, somehow change the logic so that it doesn't show the whole string but only a part
You should be able to use pytest.param to enhance the id values used. Could make a helper to determine the name based on your custom logic.
Here's an example where it strips off the last value in the string and joins them with a dash:
import pytest
list_str_1 = ["looooooooong string1"]
list_str_2 = ["loooooooooong string2"]
def make_param(args):
return pytest.param(*args, id='-'.join([s.split(" ")[-1] for s in args]))
#pytest.mark.parametrize(
"long_string_p1, long_string_p2",
list(map(make_param, zip(list_str_1, list_str_2)))
)
def test_with_long_strings(long_string_p1, long_string_p2):
assert long_string_p1 != long_string_p2
You can also supply a function as the ids argument to format each entry individually:
import pytest
list_str_1 = ["looooooooong string1"]
list_str_2 = ["loooooooooong string2"]
def id_function(val):
return val.split(" ")[-1]
#pytest.mark.parametrize(
"long_string_p1, long_string_p2",
[*zip(list_str_1, list_str_2)],
ids=id_function
)
def test_with_long_strings(long_string_p1, long_string_p2):
assert long_string_p1 != long_string_p2
End goal: I want to be able to quickly mock the input() built-in function in pytest, and replace it with an iterator that generates a (variable) list of strings. This is my current version, which works:
from typing import Callable
import pytest
def _create_patched_input(str_list: list[str]) -> Callable:
str_iter = iter(str_list.copy())
def patched_input(prompt: str) -> str: # has the same signature as input
val = next(str_iter)
print(prompt + val, end="\n"),
return val
return patched_input
#pytest.fixture
def _mock_input(monkeypatch, input_string_list: list[str]):
patched_input = _create_patched_input(input_string_list)
monkeypatch.setattr("builtins.input", patched_input)
def mock_input(f):
return pytest.mark.usefixtures("_mock_input")(f)
# Beginning of test code
def get_name(prompt: str) -> str:
return input(prompt)
#mock_input
#pytest.mark.parametrize(
"input_string_list",
(["Alice", "Bob", "Carol"], ["Dale", "Evie", "Frank", "George"]),
)
def test_get_name(input_string_list):
for name in input_string_list:
assert get_name("What is your name?") == name
However, this feels incomplete for a few reasons:
It requires the parameter name in the parameterize call to be input_string_list, which feels brittle.
If I move the fixtures into another function, I need to import both mock_input and _mock_input.
What would feel correct to me is to have a decorator (factory) that can be used like #mock_input(strings), such that you could use it like
#mock_input(["Alice", "Bob", "Carol"])
def test_get_name():
....
or, more in line with my use case,
#pytest.mark.parametrize(
"input_list", # can be named whatever
(["Alice", "Bob", "Carol"], ["Dale", "Evie", "Frank", "George"]),
)
#mock_input(input_list)
def test_get_name():
....
The latter I don't think you can do, as pytest wont recognize it as a fixture. What's the best way to do this?
I'd use indirect parametrization for mock_input, since it cannot work without receiving parameters. Also, I would refactor mock_input into a fixture that does passing through the arguments it receives, performing the mocking on the way. For example, when using unittest.mock.patch():
import pytest
from unittest.mock import patch
#pytest.fixture
def inputs(request):
texts = requests.param # ["Alice", "Bob", "Carol"] etc
with patch('builtins.input', side_effect=texts):
yield texts
Or, if you want to use monkeypatch, the code gets a bit more complex:
#pytest.fixture
def inputs(monkeypatch, request):
texts = requests.param
it = iter(texts)
def fake_input(prefix):
return next(it)
monkeypatch.setattr('builtins.input', fake_input)
yield texts
Now use inputs as test argument and parametrize it indirectly:
#pytest.mark.parametrize(
'inputs',
(["Alice", "Bob", "Carol"], ["Dale", "Evie", "Frank", "George"]),
indirect=True
)
def test_get_name(inputs):
for name in inputs:
assert get_name("What is your name?") == name
My script core.py which is found in the folder preprocessing takes a string and cleans it. It is part of a bigger model (see the last import, but it's unimportant). The dict_english, found in app/core/preprocessing/constants, is just a dictionary of uncommon English words that I replace with other words.
import string
from app.core.preprocessing.constants import dict_english
from app.core.generic.step import Step
from typing import Optional
from app.api.model.my_project_parameters import MyProjectParameters
class TextPreprocessingBase(Step[str, str]):
def process(self, input_value: str, parameters: Optional[MyProjectParameters] = None) -> str:
input_value = input_value.replace("'", '')
input_value = input_value.replace("\"", '')
printable = set(string.printable)
filter(lambda x: x in printable, input_value)
new_string=''.join(filter(lambda x: x in printable, input_value))
return new_string
class TextPreprocessingEnglish(TextPreprocessingBase):
def process(self, input_value: str, parameters: Optional[MyProjectParameters] = None) -> str:
process_english = super().process(input_value, parameters)
for word, initial in dict_english.items():
process_english = process_english.replace(word.lower(), initial)
return process_english
It's easy to test:
string_example= """ Random 'text' ✓"""
a = TextPreprocessingEnglish()
output = a.process(string_example)
print(output)
It prints:
Random text
But I want to write some automatic tests. I thought:
import pytest
from app.core.preprocessing.core import TextPreprocessingBase, TextPreprocessingEnglish
class TestEnglishPreprocessing:
#pytest.fixture(scope='class')
def english_preprocessing:
...
But I'm stuck here. I just want to test my code on several various strings that I manually write. Is it possible to do this like that or do I just write it like the simple test example above?
This sounds like something you could solve by parametrizing a test, for example:
import pytest
from process import TextPreprocessingEnglish
#pytest.mark.parametrize(
"input,expected",
[
(""" Random 'text' ✓""", "Random text"),
(""" Some other 'text' ✓""", "Some other text"),
],
)
def test_process(input, expected):
a = TextPreprocessingEnglish()
output = a.process(input)
assert output == expected
In a function I'm using uuid1 that I want to patch.
def myFunction():
my_value = uuid4.int
smth else..
I want to be able to mock my_value so it always returns the same number in my unit test, because I need it for further use.
I tried doing:
#patch('folder.myFunction.uuid4')
def test_myFunction(self, mock_value):
mock_value.return_value = 22222
But it throws an error saying myFunction does not have uuid4 as an attribute.
How do I mock its value?
The error you get is correct. Your function does not have a uuid4 attribute.
I'm reading between the lines assuming uuid4 is a method of the uuid module that normally generates a random uuid.
When testing you said you want it to always return the same value. To do that you can substitute a unittest.mock.Mock for uuid.uuid4.
In [36]: uuid_mock = Mock(return_value=uuid.UUID('77f1df52-4b43-11e9-910f-b8ca3a9b9f3e'))
In [37]: uuid_mock()
Out[37]: UUID('77f1df52-4b43-11e9-910f-b8ca3a9b9f3e')
Something like this for testing the following function (f)
import uuid, unittest
from unittest.mock import Mock, patch
def f():
z = uuid.uuid4()
return z.int
The target for the patch is the uuid method - uuid.uuid4. Specify a unittest.mock.Mock with a fixed return value for the new parameter of the patch. During the test, the Mock will be substituted for uuid.uuid4
class TestF(unittest.TestCase):
uuid_mock = Mock(return_value=uuid.UUID('77f1df52-4b43-11e9-910f-b8ca3a9b9f3e'))
good_uuid = uuid.UUID('77f1df52-4b43-11e9-910f-b8ca3a9b9f3e').int
bad_uuid = uuid.UUID('77f1df52-4b43-11e9-910f-b8ca3a9b5a31').int
#patch(target='uuid.uuid4', new=TestF.uuid_mock)
def test_myFunction_True(self):
self.assertEqual(f(), self.good_uuid)
#patch(target='uuid.uuid4', new=TestF.uuid_mock)
def test_myFunction_False(self):
self.assertNotEqual(f(), self.bad_uuid)
if __name__ == '__main__':
unittest.main()
Result:
..
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
If you want to test a function that relies on f's return value and you want f to always return the same value during testing then make f the target for the patch.
def g():
return f() == uuid.UUID('77f1df52-4b43-11e9-910f-b8ca3a9b9f3e').int
class TestG(unittest.TestCase):
good_uuid_mock = Mock(return_value=uuid.UUID('77f1df52-4b43-11e9-910f-b8ca3a9b9f3e').int)
bad_uuid_mock = Mock(return_value=uuid.UUID('77f1df52-4b43-11e9-910f-b8ca3a9b5a31').int)
#patch(target='__main__.f', new=TestG.good_uuid_mock)
def test_myFunction_True(self):
self.assertTrue(g())
#patch(target='__main__.f', new=TestG.bad_uuid_mock)
def test_myFunction_False(self):
self.assertFalse(g())
It depends on your import. Let's say you have a module called module.py, and you have an import like this:
from uuid import uuid4
This means that in this module we now have a variable called uuid4. This is the thing to mock.
#patch('path.to.module.uuid4.int')
After:
import unittest
loader = unittest.TestLoader()
tests = loader.discover('.')
testRunner = unittest.runner.TextTestRunner()
testResult = testRunner.run(tests)
I can get a list failures names and messages with:
for t in testResult.failures:
print t[0].id()
print t[1]
How to do the same for the successes?
I expect to be able to do it by overriding something in TextTestRunner. What is the easiest way?
Tested on Python 2.7.
elethan put me on the right direction: https://stackoverflow.com/a/40613695/895245
This is what I got after using TextTestRunner(resultclass= and adding a nice constructor:
import unittest
from unittest.runner import TextTestResult
class TextTestResultWithSuccesses(TextTestResult):
def __init__(self, *args, **kwargs):
super(TextTestResultWithSuccesses, self).__init__(*args, **kwargs)
self.successes = []
def addSuccess(self, test):
super(TextTestResultWithSuccesses, self).addSuccess(test)
self.successes.append(test)
loader = unittest.TestLoader()
tests = loader.discover('.')
testRunner = unittest.runner.TextTestRunner(resultclass=TextTestResultWithSuccesses)
testResult = testRunner.run(tests)
for t in testResult.failures:
print t[0].id()
print t[1]
print
for t in testResult.errors:
print t[0].id()
print t[1]
print
for t in testResult.successes:
print t.id()
I have done something similar in the past. The way I would do this is by first creating a test result class that in inherits from TestTextResult with a custom addSuccess method which will be called on each test success (addFailure is the method where the failing tests get appended to failures):
from unittest.runner import TextTestResult, TextTestRunner
class MyTestResult(TextTestResult):
successes = [] # As OP points out, this makes more sense as instance attribute
def addSuccess(self, test):
super(TextTestResult, self).addSuccess(test)
self.successes.append(test) # add other info if you want
and then make your own custom test runner, adding your custom test result as resultclass
class MyTestRunner(TextTestRunner):
resultclass = MyTestResult
# If this is your only customization, you can just pass
# the test runner class to `TextTestRunner`; see OP's answer