grails学习(二)grails单元测试

以前我在小公司,完成项目功能是终极目标。开发人员很害怕需求变化,因为他们改怕了。那问题出在哪里呢?后来我仔细想想,是没有做测试造成。那开发人员为什么如此害怕需求变化,我举个例子,a服务给b服务和c服务调用,后来需求改变,导致a服务无法满足b服务,能完成自身的功能是天大的事,于是没有和别人沟通把a服务直接改了。项目上线,突然有一天客户打电话说你们网站这里出问题,那里出问题,以前都不会的啊。你们怎么弄的。于是根据页面错误信息,开发人员很快找到错误根源,原来a服务改动,导致b服务不正常。而d,e,f服务依赖于b,那么导致d,e,f相关功能都出错了。立马动手改,改完上线,能知道的问题都没了,哈哈,真高兴,可是不能高兴太早哇,也许还有潜在bug。

软件的bug是无法避免,但是我们可以尽量减少bug,不断提升代码质量。刚我也说过,上述问题造成的原因是没有做测试。测试包括很多了,单元测试、集成测试和功能测试等等。既然测试如此重要,每完成一个类都能进行测试。

以前也许你比较纠结,没有好的工具,现在java社区非常活跃,我们可以选择的太多太多了:junit4,jmock,mockito,easymock,TestNg等等。如果你用过grails,那么你更清楚,此类快速开发框架已经帮我们集成好了。使用起来非常简单。所以今天我主要讲述下grails的单元测试。

假设需求:我们给每个用户分配工作,每个人都要完成两件事情,第一件事情:根据自己的用户名返回欢迎信息;第二件事情:根据自己的地址返回国家地区。

详细设计

用户信息类:
package com.test.domian

class User {

    
int id
    String name
    String address
    
    
static constraints = {
    }
}

工作服务接口:
package com.test.services

class WorkService {

    
/**
     * 根据用户名返回欢迎字符
     * 
@param userName
     * 
@return
     
*/
    def processWorkOne(String userName) {
    }
    
    
/**
     * 根据地址返回地区
     * 
@param address
     * 
@return
     
*/
    def processWorkTwo(String address){
    }
    
}

用户工作服务:
package com.test.services

import com.test.domian.User

class UserService {

    def workService
    
    def doWork() {
        
        def userList 
= User.list()
        userList.each {
            it.name 
= workService.processWorkOne(it.name)
            it.address 
= workService.processWorkTwo(it.address)
            
        }
    }
}

我们重点来看下测试类:
package com.test.services

import grails.test.*

import com.test.domian.User

class UserServiceTests extends GrailsUnitTestCase {
    
protected void setUp() {
        
super.setUp()
    }

    
protected void tearDown() {
        
super.tearDown()
    }

    
void testDoWork() {
        
        
//构造数据,类似于数据库存在三条记录
        def user1 = new User(id:1, name:"lucy", address:"hangzhou")
        def user2 
= new User(id:2, name:"lily", address:"wenzhou")
        def user3 
= new User(id:3, name:"lilei", address:"beijing")
        mockDomain User, [user1, user2, user3]
        
        
//mock WorkService接口的processWorkOne方法和processWorkTwo方法
        def workControl = mockFor(WorkService)
        def userCount 
= User.count()
        
while(userCount-- > 0){
            workControl.demand.processWorkOne(
1..1){String userName ->
                
return "hello world, " << userName
            }
            workControl.demand.processWorkTwo(
1..1){String address ->
                
return "location in " << address
            }
        }
        def workService 
= workControl.createMock()
        
        
//把构造好的workservice传给userservice
        UserService userService = new UserService()
        userService.workService 
= workService
        
        userService.doWork()
        
        def user4 
= User.findById(1)
        assertEquals 
"hello world, lucy", user4.name
        assertEquals 
"location in hangzhou", user4.address
    }
}

以下着重来具体说明:
1、mockDomain方法就是构造数据,包括domain类的动态方法都可以使用,比如:save(),list(),findby*()等。代码中的User.count(); User.list();就是因为调用了mockDomain方法才可以正常使用。如果是集成测试的话,grails会帮我们构造好,可以直接使用。但这里是单元测试,所以需要自己mock。

2、mockFor方法就是给WorkService构造一个对象,然后给
workControl对象的demand代理创建两个UserService中用的processWorkOne和processWorkTwo方法,代码中用到了1..1,表示mock对象只能调用这个方法一次,为什么要循环三次设置processWorkOne和processWorkTwo方法呢?因为我们在UserService是对三个对象分别进行调用处理这两件事情。也许你会想,干嘛不直接把1..3(最少调用一次,最多调用三次)。是的,我最开始也是这么来处理,可是单元测试就是同不过。
如果把
UserService类中的
workControl.demand.processWorkOne(1..1){String userName ->
      
return "hello world, " << userName
 }

改成
workControl.demand.processWorkOne(1..3){String userName ->
      
return "hello world, " << userName
 }

然后把UserServiceTests类中的:
userList.each {
            it.name 
= workService.processWorkOne(it.name)
            it.address 
= workService.processWorkTwo(it.address)
}
改成
userList.each {
            it.name 
= workService.processWorkOne(it.name)
           
it.name = workService.processWorkOne(it.name)
            it.name = workService.processWorkOne(it.name)
            it.address = workService.processWorkTwo(it.address)
}

单元测试可以通过,但是改成这样
userList.each {
            it.name 
= workService.processWorkOne(it.name)
           
it.name = workService.processWorkOne(it.name)
            it.address = workService.processWorkTwo(it.address)
           
it.name = workService.processWorkOne(it.name)
}
单元测试通不过。
以上就是表明1..3的含义:这个方法要连续被调用至少一次,至多三次。
但是有的人说我在UserService中就要这么写
userList.each {
            it.name 
= workService.processWorkOne(it.name)
           
it.name = workService.processWorkOne(it.name)
            it.address = workService.processWorkTwo(it.address)
           
it.name = workService.processWorkOne(it.name)
}
那我要怎么改单元测试才能通过?
我们把
UserServiceTests的demand这段代码
workControl.demand.processWorkOne(1..1){String userName ->
     
return "hello world, " << userName
}
workControl.demand.processWorkTwo(
1..1){String address ->
      
return "location in " << address
}

改成
workControl.demand.processWorkOne(1..2){String userName ->
      
return "hello world, " << userName
}
workControl.demand.processWorkTwo(
1..1){String address ->
       
return "location in " << address
 }

workControl.demand.processWorkOne(1..1){String address ->
       
return "location in " << address
 }
这样就通过了。
以上就是说明构造出来的函数只能按照构造的顺序调用。今天就是因为这个花了我好长时间啊,希望我理解是正确的。如有不对,请留言纠正。



posted on 2011-05-13 21:40 yangpingyu 阅读(1877) 评论(0)  编辑  收藏 所属分类: grails


只有注册用户登录后才能发表评论。


网站导航:
 
<2011年5月>
24252627282930
1234567
891011121314
15161718192021
22232425262728
2930311234

导航

统计

常用链接

留言簿

随笔分类

随笔档案

收藏夹

linux

产品交互

分析,设计,架构

安全

技术牛人

数据库

搜索

最新评论

阅读排行榜

评论排行榜