我可以编写一些单元测试,但不知道如何编写有关将其他功能连接在一起的 createAccount()的测试。
createAccount()按顺序包含一些步骤:
验证电子邮件
验证密码
检查密码是否匹配
实例化新帐户对象
每一步都有一些测试用例。 因此,我的问题是: 1.如何编写 createAccount()测试用例?我应该列出所有可能的组合测试用例然后进行测试。
例如:
TestCase0。电子邮件无效
TestCase1。应用程序重试3次后停止运行
TestCase2。电子邮件可以,密码无效
TestCase3。电子邮件可以,密码有效,第二个密码与第一个密码不匹配
TestCase4。电子邮件可以,密码有效,两个密码均匹配,安全性有效
TestCase5。电子邮件正常,密码有效,两个密码均匹配,安全性有效,帐户已成功创建
这是我的代码:
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):
"""get email from user, check email
"""
self.email = email
email_obj = Email(self.email)
status = email_obj.isValidEmail() and not accounts.isDuplicate(self.email)
if not status:
raise EmailNotOK("Email is duplicate or incorrect format")
else:
return True
def ValidatePassword(self, password):
"""
get password from user, check pass valid
"""
self.password = password
status = Password.isValidPassword(self.password)
if not status:
raise PassNotValid("Pass isn't valid")
else: return True
def CheckPasswordMatch(self, password):
"""
get password 2 from user, check pass match
"""
password_2 = password
status = Password.isMatch(self.password, password_2)
if not status:
raise PassNotMatch("Pass doesn't match")
else: return True
def createAccount(self):
retry = 0
while 1:
try:
email_input = self.view.getEmail()
self.ValidateEmail(email_input) #
break
except EmailNotOK as e:
retry = retry + 1
self.displaymessage(str(e))
if retry > 3:
return
while 1:
try:
password1_input = self.view.getPassword1()
self.ValidatePassword(password1_input)
break
except PassNotValid as e:
self.displaymessage(str(e))
while 1:
try:
password2_input = self.view.getPassword2()
self.CheckPasswordMatch(password2_input)
break
except PassNotMatch as e:
self.displaymessage(str(e))
self.seckey = self.view.getSecKey()
account = Account(Email(self.email), Password(self.password), self.seckey)
message = "Account was create successfully"
self.displaymessage(message)
return account
class Register(Option):
def execute(self):
view = RegisterUI()
controller_one = RegisterController(view)
controller_one.createAccount()
"""========================Code End=============================="""
"""Testing"""
@pytest.fixture(scope="session")
def ctrl():
view = RegisterUI()
return RegisterController(view)
def test_canThrowErrorEmailNotValid(ctrl):
email = 'dddddd'
with pytest.raises(EmailNotOK) as e:
ctrl.ValidateEmail(email)
assert str(e.value) == 'Email is duplicate or incorrect format'
def test_EmailIsValid(ctrl):
email = 'hello@gmail.com'
assert ctrl.ValidateEmail(email) == True
def test_canThrowErrorPassNotValid(ctrl):
password = '123'
with pytest.raises(PassNotValid) as e:
ctrl.ValidatePassword(password)
assert str(e.value) == "Pass isn't valid"
def test_PasswordValid(ctrl):
password = '1234567'
assert ctrl.ValidatePassword(password) == True
def test_canThrowErrorPassNotMatch(ctrl):
password1= '1234567'
ctrl.password = password1
password2 = 'abcdf'
with pytest.raises(PassNotMatch) as e:
ctrl.CheckPasswordMatch(password2)
assert str(e.value) == "Pass doesn't match"
def test_PasswordMatch(ctrl):
password1= '1234567'
ctrl.password = password1
password2 = '1234567'
assert ctrl.CheckPasswordMatch(password2)
答案 0 :(得分:2)
注意:我不太了解Python,但是我知道测试。我的Python可能并不完全正确,但是技术是正确的。
答案在于您对createAccount
的描述。它做很多事情。它具有各种验证方法的包装。它显示消息。它创建一个帐户。需要对其进行重构以进行测试。测试和重构齐头并进。
首先,对这四个片段的每个片段执行Extract Method refactoring,以将其转变为自己的方法。我只要做三个验证步骤之一,它们基本上是相同的。由于这是死记硬背的操作,因此我们可以安全地执行此操作。 Your IDE might even be able to do the refactor for you。
def tryValidatePassword(self):
while 1:
try:
password1_input = self.view.getPassword1()
self.ValidatePassword(password1_input)
break
except PassNotValid as e:
self.displaymessage(str(e))
def makeAccount(self):
return Account(Email(self.email), Password(self.password), self.seckey)
def createAccount(self):
self.tryValidatePassword()
self.seckey = self.view.getSecKey()
account = self.makeAccount()
message = "Account was create successfully"
self.displaymessage(message)
return account
只要看一下这段代码,就会发现一个错误:createAccount
不会在密码错误的情况下停止运行。
现在我们可以单独查看tryValidatePassword
并进行测试,如果密码无效,我们将看到它进入无限循环。不好我不确定循环的目的是什么,所以让我们将其删除。
def tryValidatePassword(self):
try:
password1_input = self.view.getPassword1()
self.ValidatePassword(password1_input)
except PassNotValid as e:
self.displaymessage(str(e))
现在,它只是ValidatePassword
周围的包装器,用于打印异常。这揭示了几种反模式。
首先,ValidatePassword
等正在将异常用于控制流。验证方法发现事物无效并不罕见。他们应该返回一个简单的布尔值。这简化了事情。
def ValidatePassword(self, password):
"""
get password from user, check pass valid
"""
self.password = password
return Password.isValidPassword(self.password)
现在,我们看到ValidatePassword
在做两项无关的事情:设置密码和验证密码。设置密码应该在其他地方进行。
此外doc字符串不正确,它没有从用户那里获得密码,它只是对其进行检查。删除它。从其签名可以明显看出该方法的作用,ValidatePassword验证您传入的密码。
def ValidatePassword(self, password):
return Password.isValidPassword(self.password)
另一个反模式是验证方法确定了控制器显示的消息。控制器(或可能的视图)应该控制消息。
def tryValidatePassword(self):
password1_input = self.view.getPassword1()
if !self.ValidatePassword(password1_input):
self.displaymessage("Pass isn't valid")
最后,不是从密码中传递密码,而是从对象获取密码。这是一个副作用。这意味着您不能仅通过查看方法的参数就知道方法的所有输入。这使得很难理解该方法。
有时,引用对象上的值是必要且方便的。但是这种方法做一件事:它验证密码。所以我们应该输入该密码。
def tryValidatePassword(self, password):
if !self.ValidatePassword(password):
self.displaymessage("Pass isn't valid")
self.tryValidatePassword(self.view.getPassword1())
几乎没有什么可以测试!通过此操作,我们了解了实际情况,让我们将它们重新整合在一起。 createAccount
到底在做什么?
self.view
获取内容并将其设置在self
上。1似乎是不必要的,为什么将字段从视图复制到控制器?在其他任何地方都从未引用过它们。现在我们将值传递给方法了,这不再是必需的。
2已经具有验证功能。现在一切都变瘦了,我们可以编写薄包装器来隐藏验证的实现。
4,创建帐户,我们已经分开了。
3和5,显示消息,应该与工作分开。
这是现在的样子。
class RegisterController:
# Thin wrappers to hide the details of the validation implementations.
def ValidatePassword(self, password):
return Password.isValidPassword(password)
# If there needs to be retries, they would happen in here.
def ValidateEmail(self, email_string):
email = Email(email_string)
return email.isValidEmail() and not accounts.isDuplicate(email_string)
def CheckPasswordMatch(self, password1, password2):
return Password.isMatch(password1, password2)
# A thin wrapper to actually make the account from valid input.
def makeAccount(self, email, password, seckey):
return Account(Email(email), Password(password), seckey)
def createAccount(self):
password1 = self.view.getPassword1()
password2 = self.view.getPassword2()
if !self.ValidatePassword(password1):
self.displaymessage("Password is not valid")
return
if !self.CheckPasswordMatch(password1, password2):
self.displaymessage("Passwords don't match")
return
email = self.view.getEmail()
if !self.ValidateEmail(email):
self.displaymessage("Email is duplicate or incorrect format")
return
account = self.makeAccount(email, password, self.view.getSecKey())
self.displaymessage("Account was created successfully")
return
现在,验证包装器易于测试,它们接受输入并返回布尔值。 makeAccount
也很容易测试,它接受输入并返回一个帐户(或不返回)。
createAccount
仍然做得太多。它处理从视图创建帐户的过程,但也显示消息。我们需要将它们分开。
现在该是例外的时候了!我们带回验证失败异常,但要确保它们都是CreateAccountFailed
的子类。
# This is just a sketch.
class CreateAccountFailed(Exception):
pass
class PassNotValid(CreateAccountFailed):
pass
class PassNotMatch(CreateAccountFailed):
pass
class EmailNotOK(CreateAccountFailed):
pass
现在,createAccount
如果无法创建帐户,则可以引发CreateAccountFailed
异常的特定版本。这有很多好处。呼叫createAccount
更安全。更灵活。我们可以将错误处理分开。
def createAccount(self):
password1 = self.view.getPassword1()
password2 = self.view.getPassword2()
if !self.ValidatePassword(password1):
raise PassNotValid("Password is not valid")
if !self.CheckPasswordMatch(password1, password2):
raise PassNotMatch("Passwords don't match")
email = self.view.getEmail()
if !self.ValidateEmail(email):
raise EmailNotOK("Email is duplicate or incorrect format")
return self.makeAccount(email, password, self.view.getSecKey())
# A thin wrapper to handle the display.
def tryCreateAccount(self):
try
account = self.createAccount()
self.displaymessage("Account was created successfully")
return account
except CreateAccountFailed as e:
self.displaymessage(str(e))
哇,好多。但是现在createAccount
可以轻松进行单元测试了!测试它会按预期创建一个帐户。使其引发各种异常。验证方法有自己的单元测试。
甚至tryCreateAccount
都可以测试。 Mock displaymessage
并检查是否在正确的情况下以正确的消息调用了它。
总结...