Home > Back-end >  Writing tests for python program
Writing tests for python program

Time:10-28

so I have written a python program that prompts users for letters A or B, then for a number to determine how long it will take them to accrue 1 000 000. I need help on how to write a test for the functions def profile, option_check(f) all using PYTEST.

`

import math
import sys
# Global variables

s = 1000000 # final amount
r = float(11.88/100) # convert s&p500 annual return to effective monthly rate
i = float(r / 12) # effective rate



def main():
    x = input('Do you want to:\n (A): calculate how long it will take to save your first million investing a fixed amount in the S&P500 monthly?\n OR \n (B): Calculate how much you should save in the S&P500 per month to reach 1 000 000 within your time period?\n Type A or B: ')

    letter = profile(x)
    result = option_check(letter)
    print(result)
    sys.exit()



 # do users want how long it will take or how much to save within a period
def profile(x):
    while True:
        try:
            letter = x.upper()
            if letter == 'A' or letter == 'B':
                return letter
            else:
                main()
        except ValueError:
            print('INVALID !  Please enter: A or B')

def option_check(f):
    if f == 'A':
        pay_periods, amount = option_a()
        years, months = time(pay_periods)
        result = f"It will take {years} years and {months} months investing {amount} in the S&P500 to reach 1000 000"
        return result
    if f == 'B':
        amount, time_frame = option_b()
        result = f'You will reach 1 000 000 in {time_frame} years by investing {amount} in the S&P500 Monthly'
        return result



def option_a():
    # error check for ints
    while True:
        try:
            # monthly amount
            p = int(input('How much do you plan on saving in the S&P500 per month?\n'))

            # calculate years
            top = 1 - (s * (1 - (1   i)) / p)

            periods = math.log(top) / math.log(1   i)

            return periods, p

        except:
            print('Enter valid number\n')

def time(x):
    years = math.floor(x / 12)
    months = math.floor(12 * (x / 12 - math.floor(years)))
    return years, months


def option_b():
    while True:
        try:
            time_frame = int(input('What time frame do you have to reached 1 000 000 in years?\n'))

            # calculationg p: the monthly payment needed
            periods = time_frame * 12
            top = s - s * (1   i)
            bottom = 1 - (1   i ) ** periods
            amount = round(top / bottom , 2)
            return amount, time_frame
        except:
            print('Please Enter VALID NUMBER...\n')



if __name__ == '__main__':
    main()

`

from forecast import option_check, time, profile

def main():
    test_time()
    test_option_check()


def test_time():
    assert time(12) == 1

def test_option_check():
    assert option_check('?')

def test_profile():
    assert profile('A')

if __name__ == '__main__':
    main()

since the functions return more than 1 value how would I test for this?

CodePudding user response:

Hi check123 and welcome to StackOverflow. What you are trying to accomplish is unit testing the code. This usually means that certain parts of the code will need to be patched or mocked, and your case is no exception.

First, lets look at the forecast.py file. I have added some inline comments and changed the code around a little.

forecast.py

import math
import sys
# Global variables

s = 1000000  # final amount
r = float(11.88/100)  # convert s&p500 annual return to effective monthly rate
i = float(r / 12)  # effective rate


def main():
    x = input('Do you want to:\n (A): calculate how long it will take to save your first million investing a fixed amount in the S&P500 monthly?\n OR \n (B): Calculate how much you should save in the S&P500 per month to reach 1 000 000 within your time period?\n Type A or B: ')

    letter = profile(x)
    result = option_check(letter)
    print(result)
    sys.exit()


# do users want how long it will take or how much to save within a period
def profile(x):
    try:
        letter = x.upper()
        # This if else will never raise a ValueError because if its not A or B the else just re-runs main()
        # To raise a ValueError if A or B not given, you need to raise the error within the else
        # The while True will also cause an infinite error loop, so remove that
        if letter == 'A' or letter == 'B':
            return letter
        else:
            raise ValueError()
    except ValueError:
        print('INVALID !  Please enter: A or B')


def option_check(f):
    if f == 'A':
        pay_periods, amount = option_a()
        years, months = time(pay_periods)
        result = f"It will take {years} years and {months} months investing {amount} in the S&P500 to reach 1000 000"
        return result
    if f == 'B':
        amount, time_frame = option_b()
        result = f'You will reach 1 000 000 in {time_frame} years by investing {amount} in the S&P500 Monthly'
        return result


def option_a():
    # error check for ints
    while True:
        try:
            # monthly amount
            p = int(input('How much do you plan on saving in the S&P500 per month?\n'))

            # calculate years
            top = 1 - (s * (1 - (1   i)) / p)

            periods = math.log(top) / math.log(1   i)

            return periods, p
        # Try to be specific with error handling when possible
        except ValueError:
            print('Enter valid number\n')


def time(x):
    years = math.floor(x / 12)
    months = math.floor(12 * (x / 12 - math.floor(years)))
    return years, months


def option_b():
    while True:
        try:
            time_frame = int(input('What time frame do you have to reached 1 000 000 in years?\n'))

            # calculating p: the monthly payment needed
            periods = time_frame * 12
            top = s - s * (1   i)
            bottom = 1 - (1   i) ** periods
            amount = round(top / bottom, 2)
            return amount, time_frame
        # Try to be specific with error handling when possible
        except ValueError:
            print('Please Enter VALID NUMBER...\n')


if __name__ == '__main__':
    main()

Now lets look at the test file:

test.py

import pytest
from forecast import option_check, time, profile
from unittest.mock import patch


def test_time():
    # Since time() returns a tuple we can assign a var to each member of the returned tuple
    # This will allow us to assert individually on those vars for the correct answers
    years, months = time(12)
    assert years == 1
    assert months == 0


# The mark parametrize will allow us to test multiple different inputs in one test function
@pytest.mark.parametrize("option", ["A", "B"])
def test_option_check(option):
    # Need to patch the input message for testing purposes
    # Then we can run the code with the return_value of 1 as the user input
    with patch("builtins.input", return_value=1):
        result = option_check(option)
        # This assertion allows us to assert on the correct message
        # given the option we passed in from the parametrize decorator
        assert result == ("It will take 77 years and 9 months investing 1 in the S&P500 to reach 1000 000" if option == "A" else "You will reach 1 000 000 in 1 years by investing 78892.66 in the S&P500 Monthly")


@pytest.mark.parametrize("letter", ["A", "a", "B", "b"])
def test_profile(letter):
    if letter in ["A", "a"]:
        result = profile(letter)
        assert result == "A"
    elif letter in ["B", "b"]:
        result = profile(letter)
        assert result == "B"

The main part to pay attention to here is the pytest.mark.parametrize and the patch().

Links: parametrize: https://docs.pytest.org/en/7.1.x/example/parametrize.html patch: https://docs.python.org/3/library/unittest.mock.html

Both the links above will help explain what these do. The general idea though is:

Parametrize --> This allows us to build parameters for a given test function which will parametrize the test function with the given parameters. Each parameter will build a new instance of that test function allowing us to test many parameters without having to duplicate the test function.

Mock (specifically Patch()) --> This allows us to patch an instance of the code with a return value or side effect. There is a lot to these libraries so I suggest looking into the link provided.

  • Related