Home > Software engineering >  Pytest execute specific functions but not main()
Pytest execute specific functions but not main()

Time:09-16

I am completing a python coding course from udemy, and have a project with a directory structure like:

├── LICENSE
├── README.md
├── __init__.py
├── requirements.txt
├── src
│   ├── __init__.py
│   └── milage_converter.py
└── tests
    ├── __init__.py
    └── milage_converter_test.py

Within milage_converter_test.py I am running some simple tests like:

from py_exercises.src import milage_converter


def test_base_milage_string_to_float(monkeypatch):
    # monkeypatch the "input" function, so that it returns "10".
    monkeypatch.setattr('builtins.input', lambda _: "10")
    assert milage_converter.get_base_milage(1) == 10.0

But these seem to be invoking the main() function in milage_converter.py.

def get_conversion_direction():
    """Gets the conversion direction input from a user, converts to a float and returns."""
    user_input = input(
        "Would you like to convert from (1) Miles to Kilometers or (2) Kilometers to Miles: ")
    try:
        user_input = float(user_input)
    except ValueError:
        print(
            "Invalid input, options are 1 for Miles to Kilometer or 2 for Kilometers to Miles.")
        get_conversion_direction()
    while user_input in (1, 2):
        user_input = input(
            "You chose an invalid value, please enter a value of 1 or 2: ")
    return user_input


def get_base_milage(unit):
    """Gets the milage input from a user, converts to a float and returns."""
    in_milage = input(f"Please input a value in {unit} to convert: ")
    try:
        in_milage = float(in_milage)
    except ValueError:
        print(
            "Invalid input, please input a valid numerical value to convert.")
        # Adding return avoids the exception being returned:
        # https://stackoverflow.com/a/39059651/2816893
        return get_base_milage(unit)
    return in_milage


def main():
    direction = get_conversion_direction()
    if direction == 1:
        print(
            f"You have selected {direction}, converting from Miles to Kilometers.")
        miles = int(get_base_milage("Miles"))
        print(f"{miles} Miles is equal to {round(miles * 1.609344, 2)}")
    else:
        print(
            f"You have selected {direction}, converting from Kilometers to Miles.")
        kilometers = get_base_milage("Kilometers")
        print(f"{kilometers} Kilometers is equal to {round(kilometers / 1.609344, 2)}")


main()

When I run pytest from the project directory, I get prompted for input as though I had ran the milage_converter.py module like python3.9 milage_converter.py. This is throwing an error that pytest cannot capture stdin.

------------------------------------------------------------------------------------------ Captured stdout -------------------------------------------------------------------------------------------
Would you like to convert from (1) Miles to Kilometers or (2) Kilometers to Miles: 
====================================================================================== short test summary info =======================================================================================
ERROR tests/milage_converter_test.py - OSError: pytest: reading from stdin while output is captured!  Consider using `-s`.
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
========================================================================================== 1 error in 0.10s ==========================================================================================

What I expected is that pytest would simply invoke the function specified, get_base_milage() and mock out the input. If I comment out main() on line 51 of milage_converter.py pytest does run as I expect, so I feel like I'm missing something here with pytest/mocking in python.

CodePudding user response:

If you intend to run the milage_converter.py as a script e.g. python milage_converter.py, then it is good practice to check if it is __main__ as documented:

A module can discover whether or not it is running in the main scope by checking its own name, which allows a common idiom for conditionally executing code in a module when it is run as a script or with python -m but not when it is imported:

if __name__ == "__main__":
    # execute only if run as a script
    main()

So add this to your milage_converter.py

...
if __name__ == '__main__':
    main()

The reason your test fails is because when you imported the file via from py_exercises.src import milage_converter (there is no active patch yet at this point), it of course reads the code in the file. And since your call to main() is just right there, then it would be executed too right away. Now with this condition, it would be False thus wouldn't execute it anymore.

Related reference:

  • Related