Wednesday, August 24, 2011

Functional testing Grails app with Geb. User story 1

User Story Description.
As a head of Sales department, i would like to see all orders from PreSellers and VanSellers.
I would like to see following info on the Orders Grid

  • Order Number
  • Seller
  • Customer
  • Sum
  • Customer Address
  • Status
  • Type
  • Order Date
  • Discount
  • Discount %
  • Payment type
  • Price
  • if Credit Limit Passed
  • is Stock Limit Passed
  • Shipped Date
  • Description
I also would like to be able to expand each row and see detailed description of each order, including Order Details

Architecture and Design
So, let's describe brief architecture of this solution. It's quite easy. 
We already have database solution of Orders data structure, which is on production. I prefer to provide read only access to Web Application using views.
We will use Grails Web framework as underlying technology for web application.
We will use GWT to implement views for Orders Enquiry (because i have Java background but not Java Script, unfortunately)

What about testing?
Ok. I prefer to be test driven as match as possible. But it's challenged task for UI tests. Unfortunately it's difficult to provide TDD for UI unless you have implemented it.
Imagine we will have simple grid, each row can be expanded and form appears. Form consists of 3 tabs:
  • Order Information
  • Customer Information
  • Order details with grid on it
Let's describe possible UI tests using mmm... Spock?

def "lets open order page and vlidate if 4 orders are seen"(){
when:
to OrdersPage
then:
assert ordersGrid.size == 4
}

More tests?


def "i would like to expand rows to see order info"(){
when:
to OrdersPage
and:
ordersGrid.expandRow(0).click()
then:
def now = new Date()
def df = new SimpleDateFormat('yyyy-MM-dd')
ordersGrid.expandedForm.orderInfoTab.nomNumber.value() == '000-00000-00'
ordersGrid.expandedForm.orderInfoTab.orderedDate.value() == df.format(now)
ordersGrid.expandedForm.orderInfoTab.shippedDate.value() == df.format(now)
ordersGrid.expandedForm.orderInfoTab.description.attr('value') == 'description'
ordersGrid.expandedForm.orderInfoTab.summ.value() == '10.30'
ordersGrid.expandedForm.orderInfoTab.seller.value() == 'PreSeller Ivanov'
}

def "i would like to expand rows to see customer info"(){
when:
to OrdersPage
and:
ordersGrid.expandRow(0).click()
then:
ordersGrid.expandedForm.customerInfoTab.customer.value() == 'Some test customer'
ordersGrid.expandedForm.customerInfoTab.salesOutlet.value() == 'Sales outlet'
ordersGrid.expandedForm.customerInfoTab.dept.value() == '0.00'
ordersGrid.expandedForm.customerInfoTab.creditLimit.value() == '1000.00'
}

def "i would like to expand rows to see order rows"(){
when:
to OrdersPage
and:
ordersGrid.expandRow(0).click()
then:
ordersGrid.expandedForm.orderDetailsTab.details.size() == '3'
}



What are to OrdersPage and ordersGrid.expandedForm.orderDetailsTab.details.size() == '3' you would ask. This is Geb and it's page objects.
I think that i should not explain what all these tests are doing. I think the main question is how are they doing their stuff.

Here are top level steps that you should do in order to lunch your UI tests using grails.
  • add geb grails plugin
  • add spock-groovy grails plugin
  • add build-test-data grails plugin in order to generate your data for tests
  • Place your tests in test/functional source directory
  • use grails command test-app -functional
What is grails test-app -functional
Actually this is command for triggering functional test against grails web application. The life-cycle of grails functional test is straightforward:
  • Build grails application
  • deploy it in servlet container instance (Tomcat is used)
  • start web application in tomcat
  • run functional test against running grails application
  • shutdown web application and tomcat instance
You can configure grails functional test in Continuous Integration system.

TDD
 So tests are written and it's time for coding. But it's not really true. We need to generate data for our test and do it automatically for each test run and clean previous tests results.

Test data generation
Lets create grails controller which will be responsible for data generation. And thus we will be available to trigger it from browser to generate test data.
All controllers are placed in grails-app/controllers source folder. Also there is naming convention about controllers. Their class names should have Controller tail. For instance TestController. This class will be treated as controller class. Grails has a spring MVC under the hood.
So here is our controller with two closures for generating and deleting test data for our tests

class TestController {

def generateOrders = {
OrdersBuilder.build{
orders '000-00000-00', '000-00000-01', '000-00000-02', '000-00000-03'

Date now = new Date()
order('000-00000-00').hasType('Local Storage').and().hasOrderDate(now)
order('000-00000-01').hasType('Main Stoarage ').and().hasOrderDate(DateGroovyMethods.previous(now))
order('000-00000-02').hasType('Direct Move').and().hasOrderDate(DateGroovyMethods.previous(DateGroovyMethods.previous(now)))
order('000-00000-03').hasType('indirrect Move').and().hasOrderDate(DateGroovyMethods.previous(DateGroovyMethods.previous(DateGroovyMethods.previous(now))))
}.each {
it.value.save(flush:true)
}

render 'orders generated succesfully'
}

def deleteOrders = {

Orders.findAll().each { it.delete()  }
render 'orders deleted succesfully'
}
}

And it's available through URLs
and

I've used DSL technique (which i'll describe in my future posts) for generation data in order to be more readable by user. As a result of this DSL execution we will have 4 Orders with 000-00000-00 01 02 03 order numbers. Now the only thing we should do is to visit these URLs from our test to delete previous data and generate a new one. I will explain it later

Grails application can be run against several configured environments.  Functional Tests are run against grails executed with test env. You can configure several things under environments, but one of the main things is db configuration. Test env uses embedded in-memory hsql db by default. So all generated data will not affect your production data and will live in scope of running grails server for testing propose.


Coding
Now let's implement out inquiry page using grails and GWT. I won't explain how to do it. Just provide screenshots















































Geb
Geb is Groovy based web testing framework. It uses JQuery syntax, Page Objects pattern and groovy under the hood. All these 3 elements makes Geb extremely powerful DSL for web testing and even scraping web scripts.

Spock
Spock is groovy based highly expressive specification language. It allows you describe specifications by example very quickly using given when then (and it's modifications such as expect - where) notation.


Geb Spock Grails functional tests examples



Specification provided above will open page which is represented by OrdersPage Page Object. Static variable url in OrdersPage class is used for to method in specification. As a result orders page will be opened in configured browser.
The contents of the page is described with static content closure property. The notation of defining content is following:
"name" {"defenition"}
name is a reference to the content definition which can be used in specification.
If your pages are too complicated you should use Modules. So i decided to isolate Grid object in separate ExpandableGrid Module.
As a result we will have:

when:
to OrdersPage - opens http://localhost:8080/mcell-gui/ordersControl/orders url in configured browser
then:
assert ordersGrid.size == 4 Refers to ordersGrid content definition of OrdersPage and then to size definition of ExpandableGrid Module

As you can see from ExpandableGrid i defined all needed content for my tests using css selectors. Actually Geb uses css selectors for retrieving data from the DOM tree of the page.

Also you can inherit Modules and Pages. For example i have ExpandableGrid which contains simple Grid definitions plus expandable rows functionality. Let's refactor it:


class Grid extends Module{
def debugId
static content = {
grid {
$('div', id: "gwt-debug-${debugId}").find('.x-grid3-body')
}
rows{ grid.find('table[class*=row-table]') }
size{ rows.size() }
row{row-> rows[row] }
}
}


and 


class ExpandableGrid extends Grid{
static content = {
expandRow { row ->
grid.find('.x-grid3-row-expander')[row]
}
expandedForm{ module OrderForm}
}
}

Content information will be merged with Grid Module for ExpandableGrid


One more test description




I've added more Modules to represent Order Form and all tabs on it. Now i can straightforward DSL for my specifications


Driver configuration
More detailed driver configuration you can find on geb site. I've chosen ConfigSlurper mechanism:

waiting = { timeout = 2 }
driver = {
def driver = new FirefoxDriver()
}


Now my geb scenarios will work with firefox WebDrivers


That's all so far. i think it's time to deploy may app.







No comments: