Customizing pytest parameterized test name - python

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]

Related

Can we call a pytest fixture conditionally?

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

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

Arguments in a function

I understand that when defining a function, you can provide a parameter, like so:
def func(lst):
# code
I also understand that when you are defining a function, you can provide multiple parameters using the *args syntax, like so:
def func(*lst):
# code
I have a problem where I have to define a function that sorts a list and removes any duplicates.
Here's what I did:
def func(lst):
return sorted(set(lst))
The website that I was doing this practice problem (edabit.com) tested my code, like so:
Test.assert_equals(func([1, 3, 3, 5, 5]), [1, 3, 5])
The test ran successfully, and my code was correct. But here's where I got confused, the test provided multiple parameters (1, 3, 3, 5, 5), I didn't use the *args syntax, yet somehow it ran successfully.
Isn't it supposed to give me an error, saying something like func() takes exactly 1 argument (5 given)?
When I provided the *args syntax, it told me TypeError: unhashable type:'list'
My guess is that this probably happened because the test didn't call the function, instead they used the assert keyword. Is my guess correct?
No, you gave a single argument of type list. If you have
a = [1,2,3,4]
You have a list
Calling
f(a)
And f([1, 2,3,4]) is the same
Notice the [ brackets.
If, however, you were too call f(1, 2,3,4), that would be a mistake.
Also: the assert keyword still calls the function. It has no way of not calling it, as it has been put in as an expression.
if you call f(g(5))
Then f is already called with the result of g, not the function call itself.
You passed in a list of numbers, which is one argument:
func([1, 3, 3, 5, 5])
vs.
func(1, 3, 3, 5, 5)
It looks like they only did pass a single parameter to your function.
Test.assert_equals(func([1, 3, 3, 5, 5]), [1, 3, 5]))
The array [1, 3, 3, 5, 5] is passed as the single argument to func(), then the array [1, 3, 5] is passed as the second argument to assert_equals().

Use unknown number of variables to place the returns of a method

I have a method __some_method which might return one parameter, or two, or any number. I want to call this method in another one and place the returns into variables, like so
var1, var2 ... varN = self.__some_method()
Is there any way that one can do this, in a very general setting, such that it works for any number of returned parameters?
I still think a dictionary is the best way to solve what you are trying to do. However, this does not go without saying that you should probably revise why it is you have a function that could return an indeterminate amount of variables. That seems like a bad decision. At the very least that return should be collected inside a data structure, so you know that you will have a single return type where you can deal with the contents yourself.
With that being said. If you are looking for some kind of way where you can assign names to these individual variables that will come back, you can make use of a dictionary to control this. Here is an example:
def foo(*args):
return args
result = foo(1, 2, 3, 4, 5, 6, 7, 8)
var_pack = {'var_{}'.format(idx): data for idx, data in enumerate(result, 1)}
print(var_pack)
# {'var_7': 7, 'var_4': 4, 'var_8': 8, 'var_2': 2, 'var_3': 3, 'var_6': 6, 'var_1': 1, 'var_5': 5}
print(var_pack['var_8'])
# 8

Overwriting builtins python class

What I am looking for is a way to do that in python 2.7
oldlist = list
class list(oldlist):
def append(self, object):
super(list, self).append(object)
return self
def sort(self, cmp=None, key=None, reverse=False):
super(list, self).sort(cmp, key, reverse)
return self
__builtins__.list=list
print list([3, 4, 1, 2]).append(5)
print list([3, 4, 1, 2]).append(5).sort()
print list([3, 4, 1, 2]).append(5).sort(reverse=True)
print list([3, 4, 1, 2]).append(5).sort()[0]
print [3, 4, 1, 2].append(5)
print [3, 4, 1, 2].append(5).sort()
print [3, 4, 1, 2].append(5).sort(reverse=True)
print [3, 4, 1, 2].append(5).sort()[0]
Actually print :
[3, 4, 1, 2, 5]
[1, 2, 3, 4, 5]
[5, 4, 3, 2, 1]
1
None
...
AttributeError: 'NoneType' object has no attribute 'sort'
Should print :
[3, 4, 1, 2, 5]
[1, 2, 3, 4, 5]
[5, 4, 3, 2, 1]
1
[3, 4, 1, 2, 5]
[1, 2, 3, 4, 5]
[5, 4, 3, 2, 1]
1
I know it can be dangerous to edit builtins class, but some methods really return nothing, do a python script actually expect them to return something, so what the problem ?
For now I think that much simple to do :
filterfiles(myfilelist.sort())
than doing :
myfilelist.sort()
filterfiles(myfilelist)
And it permit to see the results when in interactive mode (instead of nothing)
One thing I don't understand is that when we put {1:1, 2:2}, python look for making the dict literal into a dict object, and I know I can't change python to make an instance of mydict, but is there a way to change the builtins dict directly, whereas it use somes hacky way?
No, it’s simply not possible. Literals, that means any literal (strings, numbers, lists, dicts), are part of the Python syntax. The objects they represent are created from the parser at a very low level, long before you can change anything with Python code.
There is another important thing though. The built-in objects are all implemented in native code; they don’t actually exist as Python objects in the Python environment. For that purpose, things like __builtins__.dict provides a reference to the native dictionary type. When the objects are created with literals, the real native type is used though, so __builtins__.dict is never accessed. As such, changing __builtins__.dict will not affect it at all. It will only change the environment, where these references actually matter.
You can imagine this situation like this:
# native code
class _InternalSpecialType:
pass
def makeSpecialType (): # this is our “literal” evaluator
return _InternalSpecialType()
# public interface
SpecialType = _InternalSpecialType
# then in the Python code
class NewSpecialType(SpecialType):
pass
SpecialType = NewSpecialType
# using a “literal”
x = makeSpecialType()
print(type(x)) # _InternalSpecialType
So no, you can’t change what the literal uses under the hood. It’s simply impossible. If you want to have an object of a different type, you will always have to create it explicitely. And then it’s best to name the type differently than the original type to avoid confusion (and incompatibility between the changed type and literals).
And finally, about methods of built-in types not allowing chaining: Just live with it. Guido knowingly decided against it, and usually, Guido has good reasons you can trust, so it’s likely for the better (see also this answer).
I'll explain how to solve the problem you have, rather than how to implement the solution you're after:
Write filterfiles(sorted(myfilelist)).
Methods that return None do so by design: In this case, to avoid inadvertently sorting a list in-place (and losing its current ordering) when you really wanted a sorted copy. Python already provides functional alternatives for such cases, like sorted() in this case, when it makes sense to. Note that sorted() does not modify its argument.
If you do find a use case for which no functional alternative is provided, I would recommend you get around it in the same way: Write a function (not method) that returns what you want to see. (But check out python's functools module first).

Categories

Resources