Grails 如何重构通用Geb测试序列

Grails 如何重构通用Geb测试序列,grails,spock,geb,Grails,Spock,Geb,假设我有多个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 Log

假设我有多个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...."() {
      }
    }

如何重构/提取常见的登录“步骤”,以避免重复代码?还是我的测试写错了?谢谢。

一种可能性是有一个规范来验证实际的登录行为(例如,
LoginSpec
),该规范现在已经完全写出来了。对于在执行实际测试之前需要登录的其他规范,您可以在
LoginPage
中的方法后面抽象整个登录过程。就像您现在使用的
singIn
一样

当您有很多规范需要在他们真正开始测试他们想要测试的功能之前登录时,通过浏览器反复执行登录步骤可能需要很多时间

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

下面是我们如何在Grails+Spring安全设置中实现这一点的示例。我们还将该控制器中的其他实用方法捆绑在一起,这些方法用于设置多个规格,否则需要在浏览器中单击几下,例如更改界面语言

// 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)
    }
}

我认为“geb”的方法是使用

您可以创建如下登录模块:

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()
    }
}
@Stepwise
class ThingSpec extends GebSpec {

    def setupSpec() {
        to LoginPage
        page.login("user", "pass")
    }

    def "some test"() {
        ...
    }
}
将其包含在您的
登录页面中

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

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

    static content = {
        loginModule { module LoginModule }
    }
}
然后在测试中,您可以参考模块的
登录方法:

@Stepwise
class EditPictureSpec extends GebSpec {

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

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

您可以创建一个登录方法并将其放入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")
  }
}

正确的解决方案是在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
    }
}
从OOP的角度来看,这是最好的解决方案,因为登录过程仅适用于登录页面。使用模块是没有意义的,因为没有其他页面有登录框(除非有,否则模块是有意义的)


使用继承也是没有意义的,您将得到一堆杂乱无章的方法和类名,如“BaseSpec”,bleh。

我个人喜欢使用它,因为这样我就可以在测试中使用任意代码,而无需创建多个测试/夹具控制器。我描述了这种技术。远程控制确实是实现登录(和其他夹具设置任务)的另一个选项,无需一直在浏览器中单击。在测试设置期间,我们使用远程控制插件更改配置值,例如功能切换。但是,对于登录、语言更改等,请选择dev/test控制器方法,因为我们始终希望重新加载页面。哪种方法是您的首选方法。如果您喜欢下面的任何答案,请接受其中一个答案,或者评论为什么它们不适合您。