Skip to content
Advertisement

Refactoring Test Logic in Pytest to Avoid Complex Raises Block

I’m using the flake8-pytest-style plugin and it flags a certain test as violating PT012. This is about having too much logic in the raises() statement.

The code in question is this:

def test_bad_python_version(capsys) -> None:
    import platform
    from quendor.__main__ import main

    with pytest.raises(SystemExit) as pytest_wrapped_e, mock.patch.object(
        platform,
        "python_version",
    ) as v_info:
        v_info.return_value = "3.5"
        main()
        terminal_text = capsys.readouterr()
        expect(terminal_text.err).to(contain("Quendor requires Python 3.7"))

    expect(pytest_wrapped_e.type).to(equal(SystemExit))
    expect(pytest_wrapped_e.value.code).to(equal(1))

Basically this is testing the following code:

def main() -> int:
    if platform.python_version() < "3.7":
        sys.stderr.write("nQuendor requires Python 3.7 or later.n")
        sys.stderr.write(f"Your current version is {platform.python_version()}nn")
        sys.exit(1)

What I do is just pass in a version of Python that is less than the required and make sure the error appears as expected. The test itself works perfectly fine. (I realize it can be questionable as to whether this should be a unit test at all since it’s really testing more of an aspect of Python than my own code.)

Clearly the lint check is suggesting that my test is a little messy and I can certainly understand that. But it’s not clear from the above referenced page what I’m supposed to do about it.

I do realize I could just disable the quality check for this particular test but I’m trying to craft as good of Python code as I can, particularly around tests. And I’m at a loss as to how to refactor this code to meet the criteria.

I know I can create some other test helper function and then have that function called from the raises block. But that strikes me as being less clear overall since now you have to look in two places in order to see what the test is doing.

Advertisement

Answer

the lint error is a very good one! in fact in your case because the lint error is not followed you have two lines of unreachable code (!) (the two capsys-related lines) because main() always raises

the lint is suggesting that you only have one line in a raises() block — the naive refactor from your existing code is:

    with mock.patch.object(
        platform,
        "python_version",
        return_value="3.5",
    ):
        with pytest.raises(SystemExit) as pytest_wrapped_e:
            main()

    terminal_text = capsys.readouterr()
    expect(terminal_text.err).to(contain("Quendor requires Python 3.7"))

    expect(pytest_wrapped_e.type).to(equal(SystemExit))
    expect(pytest_wrapped_e.value.code).to(equal(1))

an aside, you should never use platform.python_version() for version comparisons as it produces incorrect results for python 3.10 — more on that and a linter for it here

Advertisement