我编写了一个 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 个值,我该如何测试?
您想要完成的是对代码进行单元测试。这通常意味着代码的某些部分需要修补或模拟,您的情况也不例外。
首先,让我们看一下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()) --> 这允许我们使用返回值或副作用来修补代码实例。这些库有很多内容,因此我建议查看提供的链接。