python - 如何编写注册账户测试用例?

标签 python unit-testing testing testng refactoring

这是关于 this post 的后续问题

在根据原始帖子的建议调整我的代码后,以下是我的完整工作代码。

但是,我有一些问题和疑问:

  • 如何测试 创建帐户 () 可以成功创建帐户还是可以抛出异常?

  • 这是我的测试,但 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() 违反这句话?它没有接受输入的参数。

  • Write functions that take input and return a result. No side effects.


  • “如果” 中的声明createAccount() 是控制流吗?
    如果是,是否违反了这句话?
    **

  • Don't use exceptions for control flow.



    ** 或者我对某事有误解?
  • Ruthlessly shave functions down until they do one thing.


  • 那么,为什么 createAccount() 做两件事呢?它从用户输入中获取值(value)然后验证
  • 我希望电子邮件输入将再次显示最多 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()
    
    
    

    最佳答案

    注:the code in the other answer不是最好的代码,但它比我们开始的地方有了很大的改进。重构的一部分是知道什么时候足够好。请记住,当您阅读本文时,可以进行更多改进,但目标是制作 createAccount()实现了可测试。

    1. This is my test but createAccount() doesn't have parameters, so how to add input to it for testing?

    createAccountself.view 获取信息.那是 RegisterUI目的。 RegisterUI的方法是交互式的,这使得它们难以在测试中使用。

    幸运的是,我们可以将任何我们喜欢的 View 传递给 RegisterController .我们不测试 RegisterUI ,它应该有自己的测试,如何RegisterController使用 RegisterUI .所以我们将制作 RegisterUI 的版本只是为了测试和使用它。

    我们可以制作 Mock object响应 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等等。将其用作 Controller 的 View 并继续。
    ctrl = RegisterController(mock_view)
    
    account = ctrl.createAccount()
    assert account.email == email
    assert account.password == password1
    assert account.seckey == seckey
    

    或者,您可以编写 RegisterUI 的子类仅用于测试在构造函数中使用其属性并覆盖 getEmail()和 friend 退货。类似于模拟,但更有条理。

    1. Does createAccount() violate [Write functions that take input and return a result. No side effects.]? It doesn't have parameters that take input.


    从技术上讲是的,但这是一个经验法则。你可以传入 view而不是使用 self.view ,但 Controller 的全部意义在于弥合 View 和模型之间的差距。它可以访问 UI 是合适的。
    createAccount()是积分函数。它封装了使用来自 UI 的信息创建帐户的过程;不需要了解 UI 的详细信息或帐户。这很好。您可以更改帐户创建过程以及调用 createAccount() 的所有内容仍然可以工作。

    1. "if" statement in createAccount() is control flow? If yes, [is this using exceptions for control flow?]


    是的,一个 if是控制流。但是createAccount()没有对控制流使用异常。

    异常(exception)是针对特殊情况。 open打开一个文件。如果它无法打开文件,则会出现异常。 createAccount()创建一个帐户。如果创建异常帐户失败,则抛出异常。

    将此与 isEmailValid(email) 之类的函数进行对比.这是询问电子邮件是否有效。使用异常(exception)来指示无效电子邮件是不合适的;完全可以预期 isEmailValid(email)将收到一封无效的电子邮件。无效的电子邮件是 isEmailValid 的正常情况。 .相反,它应该返回一个简单的 bool 值。

    但是,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. [If I should "ruthlessly shave functions down until they do one thing", why does] createAccount() do 2 things ? It get value from user input then validates.


    从外部角度来看,它只做一件事:它处理从用户输入创建帐户。究竟它是如何做到的,这是一个故意的黑匣子。这个information hiding意味着如果创建帐户的工作细节发生变化,对程序其余部分的影响是有限的。

    如果稍后它决定一个帐户需要一个名称,您可以将其添加到 createAccount() (和 RegisterUI.getName )而不更改其界面。

    1. I want to [as the user for a valid email up to 3 times]. After that, app raises exception. How to do that for easy testing?


    当我昨天处理你的代码时,我没有意识到 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()叫他们让它更 slim 。
    def createAccount(self):
        email = self.AskForValidEmail()
        password = self.AskForValidPassword()
        return self.makeAccount(email, password1, self.view.getSecKey())
    

    测试AskForValidEmail你可以让一个鸽友RegisterUI mock 。而不是 getEmail只返回一个字符串,它可以在前两次调用时返回无效的电子邮件,在第三次调用时返回有效的电子邮件。

    关于python - 如何编写注册账户测试用例?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57369574/

    相关文章:

    python - 跨平台和跨语言套接字

    python - Python中对多个Dataframes的操作

    unit-testing - 使用 Karma 在移动设备上测试 Javascript

    android-studio - 无法在Android Studio中运行录制的 Espresso 测试

    Apache 将所有请求转发到特定的静态文件

    matlab - 扩展 LIBSVM 的测试数据 : MATLAB implementation

    python - 计算两个字符串之间的唯一 ID 重叠

    python - django 休息细节_路线测试

    java - JUnit 测试 - 类调用其他类中的方法

    python - 在单元测试中将 errno 与 assertRaises 结合使用