为Python程序编写测试

问题描述 投票:0回答:1

我编写了一个 Python 程序,提示用户输入字母 A 或 B,然后输入数字来确定他们需要多长时间才能积累 1 000 000。 我需要有关如何使用 PYTEST 为 def profile、option_check(f) 函数编写测试的帮助。

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()

由于函数返回超过 1 个值,我该如何测试?

python unit-testing pytest cs50
1个回答
0
投票

您想要完成的是对代码进行单元测试。这通常意味着代码的某些部分需要修补或模拟,您的情况也不例外。

首先,让我们看一下forecast.py 文件。我添加了一些内联注释并稍微更改了代码。

预测.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()

现在让我们看一下测试文件:

测试.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"

这里要注意的主要部分是 pytest.mark.parametrize 和 patch()。

链接: 参数化:https://docs.pytest.org/en/7.1.x/example/parametrize.html 补丁:https://docs.python.org/3/library/unittest.mock.html

上面的两个链接都将有助于解释它们的作用。总体思路是:

Parametrize --> 这允许我们为给定的测试函数构建参数,这将使用给定的参数对测试函数进行参数化。每个参数将构建该测试函数的一个新实例,使我们能够测试许多参数,而无需重复测试函数。

Mock(特别是 Patch()) --> 这允许我们使用返回值或副作用来修补代码实例。这些库有很多内容,因此我建议查看提供的链接。

© www.soinside.com 2019 - 2024. All rights reserved.