How to refactor common Geb test sequences

浪子不回头ぞ 提交于 2020-01-11 09:41:29

问题


Suppose I have multiple Geb/Spock tests that beings with logging in. For example:

@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...."() {
  }    
}

And another test spec with the exact same start sequence:

@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...."() {
      }
    }

How do I refactor/extract out the common login "steps" so that I do not have duplicate code? Or am I writing my tests wrongly? Thanks.


回答1:


I think the 'geb' way to do this is to use modules.

You can create a login module like this:

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

Include it in your LoginPage:

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

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

    static content = {
        loginModule { module LoginModule }
    }
}

Then in your test, you can reference your module's login method:

@Stepwise
class EditPictureSpec extends GebSpec {

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

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



回答2:


One possibility is to have one Spec for verifying the actual login behavior (e.g. LoginSpec) that is completely written out as it is now. For other Specs that need to login before doing the actual test you can abstract the entire login process behind a method in the LoginPage. Like you do now with singIn.

When you have a lot of Specs that need to login before they can really start testing the functionality they intend to test, doing the login steps through the browser again and again can take a lot of time.

An alternative can be to create a specific controller that is only loaded in the dev/test environments and that offers a login action. So instead of going through all steps (go to page, enter name, enter password, ...) you can simply go to the URL /my-app/testLogin/auth?username=username.

Below an example how we do this in our Grails + Spring Security setup. We also bundle other utility methods in that controller that are used in the setup of multiple Specs and that would otherwise require several clicks in the browser, e.g. changing the interface language.

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



回答3:


You can create a login method and put it in a BaseSpec (that you would also create), which you would then extend in your tests. Eg:

class BaseSpec extends GebReportingSpec {

  def login(name, pw) {
    to LoginPage
    // login code here... 
  }

}

Since you're using @StepWise, I'm assuming you're logging in once per spec, so use setupSpec() thusly...

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



回答4:


The correct solution is to create methods on a geb Page which encapsulate common functionality:

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

Then your test looks like this:

@Stepwise
class ThingSpec extends GebSpec {

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

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

From an OOP perspective this is the best solution as the login procedure is only applicable to the login page. It doesn't make sense to use a module because no other page has a login box (unless it does, then modules make sense.)

It also doesn't make sense to use inheritance, you'll end up with an unorganized pile of methods and class names like "BaseSpec", bleh.



来源:https://stackoverflow.com/questions/23532633/how-to-refactor-common-geb-test-sequences

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!