【问题标题】:How to refactor common Geb test sequences如何重构常见的 Geb 测试序列
【发布时间】:2014-05-08 04:15:53
【问题描述】:

假设我有多个登录的 Geb/Spock 测试。例如:

@Stepwise
Class AddNewPictureSpec extends GebSpec {
  def "User at login page"() {
    given: "User beings from login page"
    to LoginPage
  }
  def "User gets redirected to Main page"() {
    given: "User at Login page"
    at LoginPage

    when: "User signs in"
    signIn "username", "pw"
    to MainPage

    then:
    at MainPage

  def "other test sequences follow...."() {
  }    
}

还有另一个具有完全相同开始顺序的测试规范:

@Stepwise
Class EditPictureSpec extends GebSpec {
      def "User at login page"() {
        given: "User beings from login page"
        to LoginPage
      }
      def "User gets redirected to Main page"() {
        given: "User at Login page"
        at LoginPage

        when: "User signs in"
        signIn "username", "pw"
        to MainPage

        then:
        at MainPage    

  def "other test sequences follow...."() {
      }
    }

我如何重构/提取常见的登录“步骤”,这样我就没有重复的代码?还是我写错了我的测试?谢谢。

【问题讨论】:

  • 哪种方法是您的首选。如果您喜欢以下任何答案,请接受其中一个答案或评论为什么它们不适合您。

标签: grails spock geb


【解决方案1】:

我认为执行此操作的“geb”方式是使用modules

你可以像这样创建一个登录模块:

class LoginModule extends Module {  
    static content = {
        loginForm {$("form")}
        loginButton {$("input", value: "Sign in")}
    }

    void login(String username, String password = "Passw0rd!") {
        loginForm.j_username = username
        loginForm.j_password = password
        loginButton.click()
    }
}

将其包含在您的LoginPage

class LoginPage extends Page {
    static url = "login/auth"   

    static at = {title == "My Grails Application"}

    static content = {
        loginModule { module LoginModule }
    }
}

然后在你的测试中,你可以引用你模块的login方法:

@Stepwise
class EditPictureSpec extends GebSpec {

    def setupSpec() {
        to LoginPage
        loginModule.login(loginUsername)
    }

    def "some test"() {
        ...
    }
}

【讨论】:

    【解决方案2】:

    一种可能性是拥有一个用于验证实际登录行为的规范(例如LoginSpec),该规范完全按照现在的样子写出来。对于在进行实际测试之前需要登录的其他 Specs,您可以将整个登录过程抽象到 LoginPage 中的一个方法后面。就像你现在对 singIn 所做的那样。

    当您有很多 Specs 需要登录才能真正开始测试他们打算测试的功能时,一遍又一遍地通过浏览器执行登录步骤可能会花费大量时间。

    另一种方法是创建仅在开发/测试环境中加载并提供登录操作的特定控制器。 因此,无需执行所有步骤(转到页面、输入名称、输入密码……),您只需访问 URL /my-app/testLogin/auth?username=username

    下面是我们如何在 Grails + Spring Security 设置中执行此操作的示例。我们还在该控制器中捆绑了其他 utility 方法,这些方法用于设置多个 Specs,否则需要在浏览器中单击几次,例如更改界面语言。

    // Example TestLoginController when using the Spring Security plugin
    class TestLoginController {
    
    def auth = { String userName, String startPage = 'dashboard' ->
    
        // Block the dev login functionality in production environments
        // Can also be done with filter, ...
        Environment.executeForCurrentEnvironment {
            production {
                render(status: HttpServletResponse.SC_NOT_FOUND)
                return
            }
        }
    
        def endUser = getYourEndUserDataByUsername()
        if (endUser) {
            // Logout existing user
            new SecurityContextLogoutHandler().logout(request, null, null)
    
            // Authenticate the user
            UserDetails userDetails = new User(endUser)
            def authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, userDetails.password, userDetails.authorities)
            SecurityContextHolder.context.setAuthentication(authenticationToken)
    
            // Bind the security context to the (new) session
            session.SPRING_SECURITY_CONTEXT = SecurityContextHolder.context
    
            redirect(action: "index", controller: startPage)
        }
    }
    

    【讨论】:

    • 我个人喜欢为此使用Groovy Remote Control,因为这样我就可以在我的测试中使用任意代码,而无需创建多个测试/夹具控制器。我描述了这种技术here
    • 远程控制确实是另一种实现登录(和其他夹具设置任务)的选项,而无需一直在浏览器中单击。我们使用远程控制插件来更改配置值,例如在测试设置期间功能切换。但请选择开发/测试控制器方法进行登录、语言更改等,因为我们总是希望页面重新加载。
    【解决方案3】:

    您可以创建一个登录方法并将其放入 BaseSpec(您也将创建该方法)中,然后您将在测试中对其进行扩展。例如:

    class BaseSpec extends GebReportingSpec {
    
      def login(name, pw) {
        to LoginPage
        // login code here... 
      }
    
    }
    

    由于您使用的是@StepWise,我假设您在每个规范中登录一次,因此请使用 setupSpec()...

    Class AddNewPictureSpec extends BaseSpec {
      def setupSpec() {
        login("username", "password")
      }
    }
    

    【讨论】:

      【解决方案4】:

      正确的解决方案是在geb页面上创建方法来封装通用功能:

      class LoginPage extends Page {
          static url = "login/auth"   
      
          static at = {title == "Login"}
      
          static content = {
              username { $("#user") }
              password { $("#password") }
          }
      
          def login(String email, String passwd) {
              emailInput.value(email)
              passwordInput.value(passwd)
              passwordInput << Keys.ENTER
          }
      }
      

      那么你的测试看起来像这样:

      @Stepwise
      class ThingSpec extends GebSpec {
      
          def setupSpec() {
              to LoginPage
              page.login("user", "pass")
          }
      
          def "some test"() {
              ...
          }
      }
      

      从 OOP 的角度来看,这是最好的解决方案,因为登录过程仅适用于登录页面。使用模块没有意义,因为没有其他页面有登录框(除非它有,那么模块才有意义。)

      使用继承也没有意义,你最终会得到一堆杂乱无章的方法和类名,比如“BaseSpec”,bleh。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-10-14
        • 1970-01-01
        • 2016-02-16
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多