这是此帖子上的后续问题
在调整我的代码作为原始帖子的建议之后,下面是我的完整工作代码。
但是,我有一些问题:
- 如何测试可以成功创建帐户或可以抛出异常的CreateAccount ((?
这是我的测试,但是CreateAccount((没有参数,因此如何添加输入进行测试?
def test_canCreateAccount(ctrl):
#valid email and password
email = 'hello@gmail.com'
password1 = 'beautiful'
password2 = 'beautiful'
account = ctrl.createAccount()
assert account.email == email
assert account.password == password1
- createAccount((违反了这句话吗?它没有输入的参数。
写入输入并返回结果的功能。无副作用。
- "如果" 语句 createAccount((是控制流吗?如果是,是否违反了这句话?**
不要使用异常控制流。
**或我误会了某些东西?
无情的剃须起作用,直到他们做一件事为止。
那么,为什么CreateAccount((做2件事?它从用户输入中获取值,然后验证
- 我希望将电子邮件输入再次显示长达3次。之后,应用程序提出了例外。如何进行轻松测试?
class CreateAccountFailed(Exception):
pass
class PassNotValid(CreateAccountFailed):
pass
class PassNotMatch(CreateAccountFailed):
pass
class EmailNotOK(CreateAccountFailed):
pass
class RegisterUI:
def getEmail(self):
return input("Please type an your email:")
def getPassword1(self):
return input("Please type a password:")
def getPassword2(self):
return input("Please confirm your password:")
def getSecKey(self):
return input("Please type your security keyword:")
def printMessage(self, message):
print(message)
class RegisterController:
def __init__(self, view):
self.view = view
def displaymessage(self, message):
self.view.printMessage(message)
def ValidateEmail(self, email):
email_obj = Email(email)
return email_obj.isValidEmail() and not accounts.isDuplicate(email)
def ValidatePassword(self, password):
return Password.isValidPassword(password)
def CheckPasswordMatch(self, password1, password2):
return Password.isMatch(password1, password2)
def makeAccount(self, email, password, seckey):
return Account(Email(email), Password(password), seckey)
def createAccount(self):
email = self.view.getEmail()
if not self.ValidateEmail(email):
raise EmailNotOK("Duplicate or incorrect format")
password1 = self.view.getPassword1()
if not self.ValidatePassword(password1):
raise PassNotValid("Password is not valid")
password2 = self.view.getPassword2()
if not self.CheckPasswordMatch(password1, password2):
raise PassNotMatch("Passwords don't match")
return self.makeAccount(email, password1, self.view.getSecKey())
def tryCreateAccount(self):
try:
account = self.createAccount()
self.displaymessage("Account was created successfully")
return account
except CreateAccountFailed as e:
self.displaymessage(str(e))
class Register(Option):
def execute(self):
view = RegisterUI()
controller_one = RegisterController(view)
controller_one.tryCreateAccount()
注意:另一个答案中的代码不是最好的代码,而是对我们开始的位置的巨大改进。重构的一部分是知道何时足够好。请记住,在阅读此书时,可以进行更多改进,但是实现createAccount()
可测试的目的。
- 这是我的测试,但是CreateAccount((没有参数,因此如何添加输入进行测试?
createAccount
从self.view
获取信息。那是RegisterUI
对象。RegisterUI
的方法是互动的,这使得它们难以在测试中使用。
幸运的是,我们可以将我们喜欢的任何视图传递给RegisterController
。我们不是测试RegisterUI
,它应该具有自己的测试,只是RegisterController
如何使用RegisterUI
。因此,我们将制作RegisterUI
的版本仅用于测试并使用。
我们可以制作一个对RegisterUI
的方法响应的模拟对象。
from unittest.mock import Mock
attrs = {
'getEmail.return_value': email,
'getPassword1.return_value': password1,
'getPassword2.return_value': password2,
'getSecKey'.return_value': seckey
}
mock_view = Mock(**attrs)
mock_view.getEmail()
将返回email
,依此类推。将其用作控制器的视图并进行。
ctrl = RegisterController(mock_view)
account = ctrl.createAccount()
assert account.email == email
assert account.password == password1
assert account.seckey == seckey
另外,您可以编写一个RegisterUI
的子类仅用于测试,该类别将其属性属于构造函数,并覆盖getEmail()
和朋友返回它们。类似于模拟,但组织得更加有组织。
- do createAccount((违反[写入输入并返回结果的写功能。无副作用。]?它没有输入的参数。
从技术上讲是的,但这是一个经验法则。您可以传递view
而不是使用self.view
,但是控制器的全部要点是弥合视图和模型之间的间隙。它可以访问UI是适当的。
createAccount()
是一个集成函数。它封装了使用UI信息创建帐户的过程;不需要了解UI的细节或帐户。这很好。您可以更改帐户创建过程,而称为createAccount()
的所有内容仍然可以正常工作。
- " if" createAccount((中的语句是控制流?如果是,[这是使用控制流的例外吗?]
是的,if
是控制流。但是createAccount()
不使用控制流的异常。
例外是例外案例。open
打开一个文件。如果未打开文件,您会得到异常。createAccount()
创建一个帐户。如果它无法创建一个例外的帐户,因此会引发异常。
将其与isEmailValid(email)
之类的函数进行对比。这是在询问电子邮件是否有效。使用例外来表明无效的电子邮件是不合适的;完全可以预期,isEmailValid(email)
将获得无效的电子邮件。无效的电子邮件是isEmailValid
的正常情况。相反,它应该返回一个简单的布尔。
但是,isEmailValid(email)
可能会使用异常来指示为什么电子邮件无效。例如,它可以抛出EmailIsDuplicate
以指示重复和EmailIsInvalid
以指示这是一个格式化问题。
def ValidateEmail(self, email):
email_obj = Email(email)
if !accounts.isDuplicate(email):
raise EmailIsDuplicate()
if !email_obj.isValidEmail():
raise EmailIsInvalid()
return true
然后呼叫者可以使用异常显示适当的错误。
try:
self.ValidateEmail(email)
except EmailIsDuplicate
self.displaymessage("That email is already registered.")
except EmailIsInvalid
self.displaymessage("The email is not formatted correctly.")
哪个是createAccount()
正在做的。
- [如果我应该"无情地剃光功能直到他们做一件事",为什么 ] createAccount((做2件事?它从用户输入中获取值,然后验证。
从外部角度来看,它做了一件事:它处理从用户输入创建帐户。这是故意的黑匣子。此信息隐藏意味着如果创建帐户的工作方式的细节发生了变化,则对程序的其余部分的影响受到限制。
如果以后决定一个帐户需要一个名称,则可以将其添加到createAccount()
(和RegisterUI.getName
(的情况下,而无需更改其接口。
- 我想[作为用户提供有效电子邮件最多3次]。之后,应用程序提出了例外。如何进行轻松测试?
当我昨天处理您的代码时,我没有意识到self.view.getEmail()
是互动的!这解释了无限循环。我不明白。
我们将添加另一种方法来封装,要求提供有效的电子邮件。
def AskForValidEmail(self):
for x in range(0, 3):
email = self.view.getEmail()
if self.ValidateEmail(email):
return email
else:
self.displaymessage("Email was invalid or a duplicate, please try again")
raise EmailNotOK
同样,我们会折叠询问密码并将其验证为一种方法。现在,我了解while 1
的目的,您要问他们给您有效的密码。
def AskForValidPassword(self):
while 1:
password1 = self.view.getPassword1()
password2 = self.view.getPassowrd2()
if !Password.isMatch(password1, password2):
self.displaymessage("The passwords do not match")
elif !Password.isValidPassword(password):
self.displaymessage("The password is invalid")
else
return password1
,然后createAccount()
称它们甚至更纤细。
def createAccount(self):
email = self.AskForValidEmail()
password = self.AskForValidPassword()
return self.makeAccount(email, password1, self.view.getSecKey())
要测试AskForValidEmail
,您可以制作一个奇特的RegisterUI
模拟。它可以在前两个电话中返回无效的电子邮件,而不是仅返回字符串,而不是getEmail
。
这是补充(添加更多信息(。我们需要确定测试的目的是什么。我认为以下两个原因,每个原因都导致使用相同策略的嘲笑实施。
- 确切3倍用户进入无效电子邮件后,抛出例外。
- 要验证2个无效的时间后,用户在第三次输入有效的电子邮件。
该策略是具有一个全局数组(如果有嘲笑的对象,使用对象的属性(继续跟踪调用嘲笑的次数。以下是建议。
count_try = [
'mock_3_failed': 0,
'mock_3rd_good': 0,
]
def mock_3_failed():
values = ['1st', '2nd', '3rd']
current_count = count_try['mock_3_failed']
result = values[current_count]
# When count reaches len(values) - 1 (2 for 3 element list), reset to 0
count_try['mock_3_failed'] = (current_count + 1
) if current_count < len(values) - 1 else 0
return result
def mock_3rd_good():
values = ['1st', '2nd', 'third@company.com']
current_count = count_try['mock_3rd_good']
result = values[current_count]
count_try['mock_3_failed'] = (current_count + 1
) if current_count < len(values) - 1 else 0
return result
之后,您可以具有2个测试功能。一个人使用mock_3_failed,然后断言抛出了例外。另一个使用mock_3rd_good,然后断言预期的结果。
另一种补充是重构"升高/尝试"控制流。当前,我们将逻辑知识存储在两个地方:用于检查的validateEmail功能,询问ForvalideMail报告错误。取而代之的是,我们只能重构到一个位置:validateEmail函数。这将有助于将来的代码更改。
def ValidateEmail(self, email):
email_obj = Email(email)
if !accounts.isDuplicate(email):
raise EmailNotOK("That email is already registered.")
if !email_obj.isValidEmail():
raise EmailNotOK("The email is not formatted correctly.")
return true
def AskForValidEmail(self):
MAX_TRY = 3
for x in range(0, MAX_TRY):
email = self.view.getEmail()
try:
self.ValidateEmail(email)
except EmailNotOK as e:
self.displaymessage(str(e))
raise EmailNotOK('Reached max number of trying (%d).')