gpt4 book ai didi

python - 如何用pytest编写正确的测试?

转载 作者:太空宇宙 更新时间:2023-11-03 11:12:37 24 4
gpt4 key购买 nike

我可以编写一些unittests,但不知道如何编写createAccount()的测试,它将其他函数连接在一起。
createAccount()包含以下步骤:
验证电子邮件
验证密码
检查密码匹配
实例化新帐户对象
每个步骤都有一些测试用例。
所以,我的问题是:
一。如何编写createAccount()测试用例?我应该列出所有可能的组合测试用例然后测试它们。
例如:
测试用例0电子邮件无效
测试用例1应用程序在重试电子邮件3次后停止
测试案例2电子邮件正常,密码无效
测试案例3。电子邮件正常,密码有效,第二个密码与第一个密码不匹配
测试案例4。电子邮件正常,密码有效,两个密码都匹配,安全性有效
测试案例5电子邮件正常,密码有效,两个密码都匹配,安全性有效,帐户创建成功
我不知道怎么测试吗,因为我的createAccount()很烂?如果是,如何重构它以便于测试?
这是我的代码:

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)

最佳答案

注意:我不太了解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正在做两件不相关的事情:设置密码和验证密码。设置密码应该在其他地方进行。
文档字符串也不正确,它没有从用户那里获得密码,只是检查它。删除它。从其签名可以明显看出,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并检查它是否在正确的情况下使用正确的消息调用。
总而言之…
不要对控制流使用异常。
在特殊情况下使用异常,比如不能创建帐户。
请使用异常将错误与错误处理分开。
无情地将功能与显示器分离。
无情地削减功能,直到他们做一件事。
使用瘦包装函数隐藏实现。
不要将值放在对象上,除非您确实需要对象在一个方法之外记住它们。
编写接受输入并返回结果的函数。没有副作用。

关于python - 如何用pytest编写正确的测试?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57351729/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com