如何编写注册帐户测试案例



这是此帖子上的后续问题

在调整我的代码作为原始帖子的建议之后,下面是我的完整工作代码。

但是,我有一些问题:

  1. 如何测试可以成功创建帐户或可以抛出异常的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
  1. createAccount((违反了这句话吗?它没有输入的参数。

写入输入并返回结果的功能。无副作用。

  1. "如果" 语句 createAccount((是控制流吗?如果是,是否违反了这句话?**

不要使用异常控制流。

**或我误会了某些东西?

  1. 无情的剃须起作用,直到他们做一件事为止。

那么,为什么CreateAccount((做2件事?它从用户输入中获取值,然后验证

  1. 我希望将电子邮件输入再次显示长达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()可测试的目的。


  1. 这是我的测试,但是CreateAccount((没有参数,因此如何添加输入进行测试?

createAccountself.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()和朋友返回它们。类似于模拟,但组织得更加有组织。

  1. do createAccount((违反[写入输入并返回结果的写功能。无副作用。]?它没有输入的参数。

从技术上讲是的,但这是一个经验法则。您可以传递view而不是使用self.view,但是控制器的全部要点是弥合视图和模型之间的间隙。它可以访问UI是适当的。

createAccount()是一个集成函数。它封装了使用UI信息创建帐户的过程;不需要了解UI的细节或帐户。这很好。您可以更改帐户创建过程,而称为createAccount()的所有内容仍然可以正常工作。

  1. " 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()正在做的。

  1. [如果我应该"无情地剃光功能直到他们做一件事",为什么 ] createAccount((做2件事?它从用户输入中获取值,然后验证。

从外部角度来看,它做了一件事:它处理从用户输入创建帐户。这是故意的黑匣子。此信息隐藏意味着如果创建帐户的工作方式的细节发生了变化,则对程序的其余部分的影响受到限制。

如果以后决定一个帐户需要一个名称,则可以将其添加到createAccount()(和RegisterUI.getName(的情况下,而无需更改其接口。

  1. 我想[作为用户提供有效电子邮件最多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

这是补充(添加更多信息(。我们需要确定测试的目的是什么。我认为以下两个原因,每个原因都导致使用相同策略的嘲笑实施。

  1. 确切3倍用户进入无效电子邮件后,抛出例外。
  2. 要验证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).')

最新更新