smarie / python-pytest-steps

A tiny package to ease the creation of test steps with shared intermediate results/state.
https://smarie.github.io/python-pytest-steps/
BSD 3-Clause "New" or "Revised" License
57 stars 5 forks source link

received StopIteration if yield not present. yeah? but what should i do inside an exception block #51

Closed alexzanderr closed 2 years ago

alexzanderr commented 2 years ago

i have some code

# some inputs
@pytest.mark.parametrize(
    "real_number, precision, expected_result",
    [
        (123.123, 2, "123.12"),
        ("xasd123b.x", 2, "123.123"),
        ("x.x", 2, "123.123"),
    ]
)
# some steps from your package (great btw, i love it)
@test_steps(
    "correct number of decimals ?",
    "is type str ?",
    "result == expected ?"
)
# some params for my custom function
def test_fixed_set_precision_str(
    real_number: float | str,
    precision: int,
    expected_result: float,
):
    try:
        # we know that this raises some errors 
        # when the @real_number its not valid
        result = fixed_set_precision_str(real_number, precision)
        print(result)

    except TypeError as error:
        # here its the problem with pytest-steps
        # cuz here its not yield and pytest raises error
        # because it receives StopIteration
        print(error)

    except ValueError as error:
        # same thing
        # here its the problem with pytest-steps
        # cuz here its not yield and pytest raises error
        # because it receives StopIteration
        print(error)
    else:
        _decimals = get_total_decimals(result)

        # step 1
        # correct number of decimals ?
        assert _decimals == precision
        yield

        # step 2
        # is type str ?
        assert isinstance(result, str)
        yield

        # step 3
        # result == expected ?
        assert result == expected_result
        yield

in pytest output i do get this error:

ytest_steps.steps_generator.StepExecutionError: Error executing step 'correct number of decimals ?': could not reach the next `yield` statement (received
 `StopIteration`). This may be caused by use of a `return` statement instead of a `yield`, or by a missing `yield`

i understand why, but i dont know how to fix this, because steps is always expecting a generator.

thanks in advance.

alexzanderr commented 2 years ago

it fails only when my custom function is raising an exception which is catched in the except block.

smarie commented 2 years ago

Thanks @alexzanderr for posting this issue.

The problem is that when you catch the exception in the first step, you do not perform the other two steps. In other words, in your function there are not always three"yield" statements reached.

Why do you actually try/catch the exception in the first step ? You should only do this if this exception is normal. Otherwise, simply let pytest fail :) If the exception is normal and it is legitimate to catch it, then you should write two other steps in the catch branch. Each step should end with "yield".

alexzanderr commented 2 years ago

The problem is that when you catch the exception in the first step, you do not perform the other two steps. In other words, in your function there are not always three"yield" statements reached.

yes i know, that.

Why do you actually try/catch the exception in the first step ? You should only do this if this exception is normal. Otherwise, simply let pytest fail :)

im doing that because im making sure that my function is raising the correct type of exception based on the wrong type params or wrong values of params that it got.

smarie commented 2 years ago

You might be interested in using with pytest.raises to actually perform such exception raising checks in a "pytest way".

Anyway whatever you decide, the solution is still very easy: make sure that you have three steps in all branches of your code, and that will work. For example:

    try:
        # we know that this raises some errors 
        # when the @real_number its not valid
        result = fixed_set_precision_str(real_number, precision)
        print(result)

    except TypeError as error:
        print(error)
        # step 1
        yield
        # step 2
        yield
        # step 3
        yield

    except ValueError as error:
        print(error)
        # step 1
        yield
        # step 2
        yield
        # step 3
        yield
    else:
        _decimals = get_total_decimals(result)

        # step 1
        # correct number of decimals ?
        assert _decimals == precision
        yield

        # step 2
        # is type str ?
        assert isinstance(result, str)
        yield

        # step 3
        # result == expected ?
        assert result == expected_result
        yield

Indeed pytest does not allow for dynamic modification of the number of tests. So if you declare three steps then there must be three tests, and the only way pytest-steps is able to know what you put inside is to look at the yield statements.

alexzanderr commented 2 years ago

yes, indeed, the solution is to repeat fake yields inside the except block.

        # step 1
        yield
        # step 2
        yield
        # step 3
        yield

thanks a lot for support!

smarie commented 2 years ago

You're welcome!