问题
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