Can we call a pytest fixture conditionally? - python

My use case is to call fixture only if a certain condition is met. But since we need to call the pytest fixture as an argument to a test function it gets called every time I run the test.
I want to do something like this:
#pytest.parameterize("a", [1, 2, 3])
def test_method(a):
if a == 2:
method_fixture

Yes, you can use indirect=True for a parameter to have the parameter refer to a fixture.
import pytest
#pytest.fixture
def thing(request):
if request.param == 2:
return func()
return None
#pytest.mark.parametrize("thing", [1, 2, 3], indirect=True)
def test_indirect(thing):
pass # thing will either be the retval of `func()` or NOne
With dependent "fixtures"
As asked in the edit, if your fixtures are dependent on each other, you'll probably need to use the pytest_generate_tests hook instead.
E.g. this will parametrize the test with values that aren't equal.
import itertools
def pytest_generate_tests(metafunc):
if metafunc.function.__name__ == "test_combo":
a_values = [1, 2, 3, 4]
b_values = [2, 3, 4, 5]
all_combos = itertools.product(a_values, b_values)
combos = [
pair
for pair in all_combos
if pair[0] != pair[1]
]
metafunc.parametrize(["a", "b"], combos)
def test_combo(a, b):
assert a != b

The answer is accepted and helped for the OP however it is not "conditional fixture calling". It is called always only it behaves differently based on some condition.
So I only want to clarify that real conditionally call (or dynamically run) a fixture is possible using the request fixture.
#pytest.parameterize("a", [1, 2, 3])
def test_method(request, a):
if a == 2:
request.getfixturevalue('method_fixture')
See documentation here https://docs.pytest.org/en/7.1.x/reference/reference.html#pytest.FixtureRequest.getfixturevalue

Related

Customizing pytest parameterized test name

I've the following tests:
#pytest.mark.parametrize(
"nums",
[[3, 1, 5, 4, 2], [2, 6, 4, 3, 1, 5], [1, 5, 6, 4, 3, 2]]
)
def test_cyclic_sort(nums):
pass
#pytest.mark.parametrize(
"nums, missing",
[([4, 0, 3, 1], 2)]
)
def test_find_missing_number(nums, missing):
pass
I'd like to customize the test names to include the input array. I've read the pytest docs, and this question and this question, but none answer the following questions:
What is passed to the id func? In my code above, the first test takes one parameter, the second takes two.
pytest docs use a top-level function for id, whereas I'd like to put my tests in a class and use a #staticmethod. Trying to reference the static method with TestClass.static_method from inside TestClass gives an error in PyCharm; what is the correct syntax for doing this?
Edit:
Created https://github.com/pytest-dev/pytest/issues/8448.
When using a callable for the ids keyword, it will be called with a single argument: the value of the test parameter being parametrized. The callable ids return a string, which will be used in square brackets as the test name suffix.
If the test is parametrizing over multiple values, the function will still be called with a single argument, but it will be called multiple times per test. The generated name will be joined with dashes, something like
"-".join([idfunc(val) for val in parameters])
For example:
test_something[val1-val2-val3]
Here is the join in the pytest source.
To use a static method, this syntax works:
class TestExample:
#staticmethod
def idfunc(val):
return f"foo{val}"
#pytest.mark.parametrize(
"x, y",
[
[1, 2],
["a", "b"],
],
ids=idfunc.__func__,
)
def test_vals(self, x, y):
assert x
assert y
This will generate two tests, calling idfunc four times as described above.
TestExample::test_vals[foo1-foo2]
TestExample::test_vals[fooa-foob]
I like wims answer, and this is intended as a comment to his answer (I dont have the points to make a comment). This seems more pythonic to me. It also helps avoid using a static method.
class TestExample:
#pytest.mark.parametrize(
"x, y",
[
[1, 2],
["a", "b"],
],
ids= lamba val : f"foo{val}"
)
def test_vals(self, x, y):
assert x
assert y
This will have the same output:
TestExample::test_vals[foo1-foo2]
TestExample::test_vals[fooa-foob]

"Pythonic" class level recursion?

I am creating a class that inherits from collections.UserList that has some functionality very similar to NumPy's ndarray (just for exercise purposes). I've run into a bit of a roadblock regarding recursive functions involving the modification of class attributes:
Let's take the flatten method, for example:
class Array(UserList):
def __init__(self, initlist):
self.data = initlist
def flatten(self):
# recursive function
...
Above, you can see that there is a singular parameter in the flatten method, being the required self parameter. Ideally, a recursive function should take a parameter which is passed recursively through the function. So, for example, it might take a lst parameter, making the signature:
Array.flatten(self, lst)
This solves the problem of having to set lst to self.data, which consequently will not work recursively, because self.data won't be changed. However, having that parameter in the function is going to be ugly in use and hinder the user experience of an end user who may be using the function.
So, this is the solution I've come up with:
def flatten(self):
self.data = self.__flatten(self.data)
def __flatten(self, lst):
...
return result
Another solution could be to nest __flatten in flatten, like so:
def flatten(self):
def __flatten(lst):
...
return result
self.data = __flatten(self.data)
However, I'm not sure if nesting would be the most readable as flatten is not the only recursive function in my class, so it could get messy pretty quickly.
Does anyone have any other suggestions? I'd love to know your thoughts, thank you!
A recursive method need not take any extra parameters that are logically unnecessary for the method to work from the caller's perspective; the self parameter is enough for recursion on a "child" element to work, because when you call the method on the child, the child is bound to self in the recursive call. Here is an example:
from itertools import chain
class MyArray:
def __init__(self, data):
self.data = [
MyArray(x) if isinstance(x, list) else x
for x in data]
def flatten(self):
return chain.from_iterable(
x.flatten() if isinstance(x, MyArray) else (x,)
for x in self.data)
Usage:
>>> a = MyArray([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
>>> list(a.flatten())
[1, 2, 3, 4, 5, 6, 7, 8]
Since UserList is an iterable, you can use a helper function to flatten nested iterables, which can deal likewise with lists and Array objects:
from collections import UserList
from collections.abc import Iterable
def flatten_iterable(iterable):
for item in iterable:
if isinstance(item, Iterable):
yield from flatten_iterable(item)
else:
yield item
class Array(UserList):
def __init__(self, initlist):
self.data = initlist
def flatten(self):
self.data = list(flatten_iterable(self.data))
a = Array([[1, 2], [3, 4]])
a.flatten(); print(a) # prints [1, 2, 3, 4]
b = Array([Array([1, 2]), Array([3, 4])])
b.flatten(); print(b) # prints [1, 2, 3, 4]
barrier of abstraction
Write array as a separate module. flatten can be generic like the example implementation here. This differs from a_guest's answer in that only lists are flattened, not all iterables. This is a choice you get to make as the module author -
# array.py
from collections import UserList
def flatten(t): # generic function
if isinstance(t, list):
for v in t:
yield from flatten(v)
else:
yield t
class array(UserList):
def flatten(self):
return list(flatten(self.data)) # specialization of generic function
why modules are important
Don't forget you are the module user too! You get to reap the benefits from both sides of the abstraction barrier created by the module -
As the author, you can easily expand, modify, and test your module without worrying about breaking other parts of your program
As the user, you can rely on the module's features without having to think about how the module is written or what the underlying data structures might be
# main.py
from array import array
t = array([1,[2,3],4,[5,[6,[7]]]]) # <- what is "array"?
print(t.flatten())
[1, 2, 3, 4, 5, 6, 7]
As the user, we don't have to answer "what is array?" anymore than you have to answer "what is dict?" or "what is iter?" We use these features without having to understand their implementation details. Their internals may change over time, but if the interface stays the same, our programs will continue to work without requiring change.
reusability
Good programs are reusable in many ways. See python's built-in functions for proof of this, or see the the guiding principles of the Unix philosophy -
Write programs that do one thing and do it well.
Write programs to work together.
If you wanted to use flatten in other areas of our program, we can reuse it easily -
# otherscript.py
from array import flatten
result = flatten(something)
Typically, all methods of a class have at least one argument which is called self in order to be able to reference the actual object this method is called on.
If you don't need self in your function, but you still want to include it in a class, you can use #staticmethod and just include a normal function like this:
class Array(UserList):
def __init__(self, initlist):
self.data = initlist
#staticmethod
def flatten():
# recursive function
...
Basically, #staticmethod allows you to make any function a method that can be called on a class or an instance of a class (object). So you can do this:
arr = Array()
arr.flatten()
as well as this:
Array.flatten()
Here is some further reference from Pyhon docs: https://docs.python.org/3/library/functions.html#staticmethod

Extract unittest.mock call arguments agnostically w.r.t whether they have been passes as positional arguments or keyword arguments

This question is similar to check unittest.mock call arguments agnostically w.r.t. whether they have been passed as positional arguments or keyword arguments, but with an extension.
Given a mocked function with a given spec, it is possible to check whether a function was called with the right arguments, regardless of whether the function was called with positional arguments or keyword arguments (or a mixture of both). So obviously, mock knows how to translate between those two.
My problem is a little more complicated, because given this signature:
def myfun(a, b, some_list, c=3, d=4)
# Do something with a, b, c, d and some_list
return None
all the following calls should be regarded as correct:
myfun(b=2, a=1, some_list = [3, 4, 5])
myfun(1, 2, [5, 4, 3])
myfun(1, 2, d=4, some_list=[4, 3, 5])
The values for a, b, c and d should be 1, 2, 3 and 4 respectively, but the value for some_list should just contain the values [3, 4, 5], without regard to order.
What I would like to do in a unittest is something like this (when complicated_fun should at one point call myfun with the given arguments):
def test_complicated_fun(self):
myobject = MyClass(...)
myobject.myfun = Mock(return_value = None, spec=myobject.myfun)
myobject.complicated_fun()
myobject.myfun.assert_called_once()
self.assertEqual(myobject.myfun.call_args['a'], 1)
self.assertEqual(myobject.myfun.call_args['b'], 2)
self.assertEqual(myobject.myfun.call_args['c'], 3)
self.assertEqual(myobject.myfun.call_args['d'], 4)
self.assertCountEqual(myobject.myfun.call_args['mylist'], [3, 4, 5])
Except this will of course fail, seeing as we can't subscript call_args.
Is there a way around this, and to get the value of mylist directly from the mock?
I could make a workaround it by using myobject.myfun.call_args[1].get('mylist', myobject.myfun.call_args[0][2]).
But that:
Depends on manually specifying again that mylist is the argument at index 2, meaning if my spec changes I should also manually edit the test.
Doesn't feel very pythonic

Unit test a Mutated List

I have a function that is intended to mutate a list.
I wanted to build some unit tests for this function, but I don't know how to test for a change in values when the function doesn't return something...
For example:
def square_list(lst):
"""
Input: a list of numbers
Doesn't return anything, mutates given list
"""
for i in range(len(lst)-1):
lst[i] **= 2
Obviously I can build a unit test for this, but without a retun value it won't work
import unittest
from square_list import square_list
class TestSquareList(unittest.TestCase):
def test_square_list(self):
self.assertEqual(square_list([1, 2]), [1, 4])
self.assertEqual(square_list([0]), [0])
How would you test this function mutates the input to the function appropriately?
You can set it up like this:
import unittest
from square_list import square_list
class TestSquareList(unittest.TestCase):
def test_square_list(self):
provided = [1, 2]
expected = [1, 4]
square_list(provided) # mutates provided
self.assertEqual(provided, expected)

How do you test a mutable, implied result?

I have a function that doesn't "return" anything but relies on altering a dictionary/list using its mutability.
i.e.:
def func(my_list):
my_list.append(4)
I want to test this function using pytest and parameterisation:
#pytest.mark.parametrize("input1, result", [
([1], [1, 4]),
([33, 44], [33,44, 4])
])
def test_mytest(input1, result):
assert func(input1) == result
Problem is, this obviously won't work because my function doesn't actually "return" my_list.
Is it possible to test the value of my_list using pytest, and if so how?
You could compare the value of list1 to result after the call to the function.
def test_mytest(input1):
func(input1)
assert input1 == result
Note that input1 will be modified after the call to the function and can now be compared to the expected result

Categories

Resources