Unit test a Mutated List - python

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)

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

"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

Copy a variable in python (jupyter) / Use different functions with same variables

I wrote a small Programm in python but it don't work like expected.
Here's the code:
puzzle = [8, 7, 5, 4, 1, 2, 3, 0, 6]
def count(p):
p[0] += 1
return p
def main(p):
print(p)
l = count(p)
print(l)
print(p)
b1 = main(puzzle)
I expect that print(p) will be different from print(l), but the result of both is the same, it's the result that print(l) should have. But p did change also, however I would need it to be unchanged… Is this a special python behavior? Is there something I missed?
I also tried to change the variable names in the functions, but that didn't help.
I restarted the Compiler, but that didn't help either.
Is there a solution to store a function output and than call the function again without let the function change the given parameters?
So that l will be the result after the calculation and p will stay the value before?
Kind Regards,
Joh.
You are passing a List parameter. Parameter passing is Call-by-Object. Since a List is a mutable object in this situation it is similar to pass by reference and changes to your List object will persist. If you were passing an immutable, such as an Integer or String, it would be akin to pass by copy/value, and changes would not persist. E.g.:
def s2asdf(s):
s = "asdf"
s = "hello world"
s2asdf(s)
print s
... results in:
$ python example.py
hello world
The reason for this is because Python passes function parameters by reference. When you call the count function it allows the function to modify the list inside the function and the changes will be applied to the original object.
If you want to have the function not modify the list but instead return a different list, you will have to make a copy of the list either by passing a copy to the function or make a copy inside the function itself. There are many ways to copy a list in Python, but I like to use the list() function to do it.
This should fix your problem:
puzzle = [8, 7, 5, 4, 1, 2, 3, 0, 6]
def count(p):
new_list = list(p) # copy values of p to new_list
new_list[0] += 1
return new_list
def main(p):
print(p)
l = count(p)
print(l) # l is the new_list returned from count
print(p) # p stays the original value
b1 = main(puzzle)

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

Rotating left int list with deque returns None

I am just wondering if there's a syntax error on my end. I have an int list and I want to rotate them left. I used deque and rotate(-1) but it's returning None.
An example of outcome I am looking for:
list1 = [1, 2 , 3]
rotateLeft = [2, 3, 1]
This is the code snippet.
from collections import deque
def rotate_left(nums):
return deque(nums).rotate(-1)
print rotate_left([1, 2, 3])
>>> None
Can anyone tell me if I did something wrong?
deque.rotate returns nothing.
So rotate_left should be:
def rotate_left(nums):
q = deque(nums)
q.rotate(-1)
return list(q)
Almost functions/methods in standard library that modify data return nothing.
(Exception: dict.setdefault, ..)

Categories

Resources