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
Related
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
How do I write multiple test cases for a single function. I want the functions to essentially iterate through a bunch of tests and then return a boolean or answer saying that the function has passed these tests.
def test_valid01():
name = 'jack'
expected = True
assert is_valid_name(name) == expected, "is_valid_name has failed."
test1 = True
this is an example of one of my functions testing another function.
A way that you could test various tests for the is_valid_name() function:
def assert_test_valid(name, expected_value):
assert is_valid_name(name) == expected_value, f"is_valid_name has failed for {name}" # allows identification of which test failed
def test_valid_all():
test_responses = { # the input and the expected response
'jack': True,
}
for key, value in test_responses.items(): # to go through all of the responses that you would like to test
assert_test_valid(key, value)
Let's say I have a Python method:
def good_method(self) -> None:
txt = "Some text"
response = self.bad_method(txt)
resources = response["resources"]
print (resources)
while resources:
response = self.bad_method(txt)
resources = response["resources"]
print (resources)
Now let's say I want to write a unit test for it. The bad_method() returns a dictionary and it could get called over and over in the while loop. I have been trying to Mock the bad_method() so it returns a nested dictionary, so the while loop runs once. This is the code:
from unittest.mock import MagicMock
def test_good_method():
dic = {"resources": {"resources": "values"}}
def side_effect():
return dic
self.bad_method() = MagicMock(side_effect=side_effect())
self.good_method()
I expected to first get a {"resources": "values"} printed out, and then a values. But the only thing I get is a resources. What am I doing wrong? How can I achieve what I expect?
def test_good_method():
thing = MyClassContainingGoodAndBadMethod()
thing.bad_method = MagicMock(return_value={"a": "b"})
thing.good_method()
assert thing.bad_method.call_count == 10 # or however many times it is supposed to be called
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)
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