alfredodeza / pytest.vim

Runs your UnitTests with py.test displaying red/green bars and errors
274 stars 40 forks source link

`:Pytest function` doesn't work with nested functions #78

Closed dwhswenson closed 1 year ago

dwhswenson commented 2 years ago

Nested functions within bare test functions don't find the test function correctly. This is basically the same problem as #24, but it looks like the fix for that (#25) may have missed this use case.

def test_inner_func():  # :Pytest function works here
    def inner_func():  # :Pytest function fails here and below
        return "foo"

    assert inner_func() == "foo"
    # ERROR: not found: ~/tmp/pytest.vim/test_example.py::inner_func

vim 8.2; pytest.vim @ ba2bab8

alfredodeza commented 1 year ago

Hey @dwhswenson nice catch! There must be a good reason why the code has nested functions in a test case, but can you help clarify what the use case is here?

Would it be reasonable to say that any nested function that is not prefixed with test_ should be ignored?

dwhswenson commented 1 year ago

There must be a good reason why the code has nested functions in a test case, but can you help clarify what the use case is here?

Looking back, I'm guessing this may have been the code I was working on at the time (it was probably something in that repo):

https://github.com/openpathsampling/openpathsampling-cli/blob/f0c772215960820202ecf4ba37f1fbf73a309d8b/paths_cli/tests/wizard/test_cvs.py#L19-L34

What I'm doing there is defining a one-off patch for a single instance, which allows me to isolate this test from the actual register mechanism (which would have required a bunch of file IO plus a lot of boilerplate code that I avoid here).

Another use case is if the thing being tested takes a function as input (e.g., decorator) and maybe you want to test an error condition (so the function used as input in the test won't be used elsewhere). Here's a little toy I put together:

import pytest
import inspect

def itemify_decorator(func):
    # toy: take values given as length-1 dict and turn them into 2 args for
    # the wrapped function; fail early if wrapped func isn't a 2-parameter
    # function
    if len(inspect.signature(func).parameters) != 2:
        raise ValueError(f"Bad function: {func}")

    def inner(dct):
        if len(dct) > 1:
            raise ValueError(f"dct should only have 1 entry")
        func(*list(dct.items())[0])

    return inner

def test_error_if_one_param():
    def bad_func(one_param):
        pass

    with pytest.raises(ValueError, match="Bad function"):
        decorated = itemify_decorator(bad_func)

I'm testing the early fail condition if the function signature is wrong, so I only need bad_func in that one test, and so I'd usually define it inside the test function's scope.

Would it be reasonable to say that any nested function that is not prefixed with test_ should be ignored?

I think so -- I can't think of a case where I would define a nested function starting with test_ (and if I did, I wouldn't be at all surprised that the plugin found and tried to run the nested function -- it would happen once and then I'd name my nested functions differently!)

Thanks for taking a look at this!

alfredodeza commented 1 year ago

Can you try the latest commit pushed to master? It is https://github.com/alfredodeza/pytest.vim/commit/6303575b5b4379326ae692767684b1c6b34c59b1

I used your example to ensure it would work fine.

Screen Shot 2022-11-24 at 10 36 42 AM
alfredodeza commented 1 year ago

Closing this since I've verified it works. Feel free to reopen otherwise.