mercredi 6 mai 2015

How do I make sure that the function under test is the only function being called?

I'm using Python, if that's relevant.

As I understand it, a unit test is a test of the smallest portion of code that is relevant to a single use case. Often times this is a single function or method.

So when I'm testing my function, I do not want to test any functions that it's calling. When writing tests, that's reasonably easy to do: Just concern yourself with the logic that your function is doing.

But when I have the following situation:

def is_prime(number):
    if number <= 1:
        return False

    for element in range(2, number):
        if number % element == 0:
            return False

    return True

def extract_primes(lst):
    return [item for item in list if is_prime(item)]

I'm confused about how a test for extract_primes would look like.

def test_extract_primes():
    lst = [0, 1, 2]
    result = extract_primes_from_list(lst)
    assert result == [2]
    # and some more similar tests for empty lists, lists with only primes
    # and lists with no primes

But now I'm just implicitly testing whether is_prime is doing its work correctly. And when is_prime has a bug, it will also cause the test for extract_primes to fail, which beats the point of unit tests, where you want to quickly spot the single point of failure.

So it seems to me, that I shouldn't be calling is_prime in the first place. So I end up with something like this:

@unittest.patch("path.to.module.is_prime")
def test_extract_primes(mock_is_prime):
    mock_is_prime.return_value = True
    lst = list(range(10))
    result = extract_primes(lst)
    assert result == lst
    for i in lst:
        assert mock_is_prime.called_with(i)  # not sure if correct method name

But this is tiresome and inflexible at best. I cannot cause mock_is_prime to return different values inside of the same test, unless I create a construct like:

bool_list = [True, False, True]
mock_is_prime.side_effect = lambda x: next(bool_list)

But that gets even more verbose. And as the number of function calls within the function under test increases, so does the stupid amount of boilerplate code to patch away those functions.

When I try to look for answers on the internet, I mostly get Java/C# dependency injection instructions, where they tell you to pass the correct needed object as parameter, and just create a dummy of that object for the method you're testing. Or I get debates about whether or not to test private methods. And that's fine, I understand those things. But I can't for the life of me figure out what to do with functions that depend on other function calls. Should I simply inject those functions?

def extract_primes(lst, is_prime_function=is_prime):
    return [item for item in list if is_prime_function(item)]

And pass a dummy is_prime function into extract_primes under test? That just looks stupid and litters the function signature with weird parameters. And it's practically no different from patching the function away in the amount of work required. It just removes a single @patch statement from the test.

So should I not be patching away functions in the first place? And if not, at what point does a function become worth patching away anyway? Only when it's manipulating the system? Or also when it's from a completely separate module?

I'm a little lost.

Aucun commentaire:

Enregistrer un commentaire