首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

精通 Grails 使用 Grails 进行单元测试(3)

精通 Grails 使用 Grails 进行单元测试(3)

使用 mockForConstraintsTests() 测试 unique 约束在上一节已经看到,可以在隔离环境中轻松执行对他多数约束的测试。例如,测试 password 字段的 minSize 至少为 5 非常简单,因为它只依赖于字段本身的值。清单 10 给出了 testPassword() 方法:
清单 10. 测试 minSize 约束
1
2
3
4
5
6
void testPassword() {
  mockForConstraintsTests(User)
  def user = new User(password:"foo")
  assertFalse user.validate()
  assertEquals "minSize", user.errors["password"]
}




但是如何测试 unique 这样的约束呢?这种约束确保数据库表不包含重复值。幸运的是,mockForConstraintsTests() 还接受第二个参数:一个用于模拟真实数据库表的域类列表(替代真实的数据库表)。清单 11 演示了使用模拟表测试 unique 约束的过程:
清单 11. 使用模拟表测试 unique 约束
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void testUniqueLogin(){
  def jdoe = new User(name:"John Doe",
                      login:"jdoe",
                      password:"password")

  def suziq = new User(name:"Suzi Q",
                       login:"suziq",
                       password:"wordpass")

  mockForConstraintsTests(User, [jdoe, suziq])

  def jane = new User(login:"jdoe")
  assertFalse jane.validate()
  assertEquals "unique", jane.errors["login"]
}




在内存中模拟数据库表可以节省大量时间,尤其是在启动实际数据库需要很长时间时。更糟的是,一旦数据库开始运行,您仍然需要确保使用使您的断言得以通过所必需的记录来填充数据库表。
我并不是暗示运行对生产数据库运行实际的集成测试时浪费时间。我的意思是,这些耗时的集成测试更适合于持续集成服务器。在这种情况下,模拟数据库交互可以使您专注于 Grails 功能,只花少部分时间来进行测试。
模拟数据库表已超出了 mockForConstraintsTests() 方法的能力范围。您可以使用 mockDomain() 方法完成这件事。
理解 mockDomain()GORM 将一些有用的方法元编程到域类上: save()、list() 和许多定位程序,比如 findAllByRole()。顾名思义,mockForConstraintsTests() 方法将验证方法添加到域类上,以进行测试。mockDomain() 方法将持久性方法添加到域类上,以进行测试。清单 12 展示了 mockDomain() 方法的实际应用:
清单 12. 使用 mockDomain() 测试 GORM 方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
void testMockDomain(){
  def jdoe = new User(name:"John Doe", role:"user")
  def suziq = new User(name:"Suzi Q", role:"admin")
  def jsmith = new User(name:"Jane Smith", role:"user")

  mockDomain(User, [jdoe, suziq, jsmith])

  //dynamic finder
  def list = User.findAllByRole("admin")
  assertEquals 1, list.size()

  //NOTE: criteria, Hibernate Query Language (HQL)
  //      and Query By Example (QBE) are not supported
}




mockDomain() 方法尽可能忠实地建模 GORM 行为。例如,当您将一个域类保存到模拟表在中时,会像在实际应用程序中一样填充 id 字段。id 值只是列表中元素的序数值。清单 13 展示了在单元测试中保存 域类:
清单 13. 将一个域类保存到单元测试中
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void testMockGorm(){
  def jdoe = new User(name:"John Doe", role:"user")
  def suziq = new User(name:"Suzi Q", role:"admin")
  def jsmith = new User(name:"Jane Smith", role:"user")

  mockDomain(User, [jdoe, suziq, jsmith])

  def foo = new User(login:"foo")
  foo.name = "Bubba"
  foo.role = "user"
  foo.password = "password"
  foo.save()
  assertEquals 4, foo.id  //NOTE: id gets assigned
  assertEquals 3, User.findAllByRole("user").size()
}




模拟底层数据库并不是您唯一可以在 GrailsUnitTestCase 中完成的工作。您也可以模拟日志基础架构。
理解 mockLogging()GrailsUnitTestCase 的用途并不仅仅是测试域类。键入 grails create-service Admin 创建一个 Admin 服务,如清单 14 所示:
模拟和元编程的局限性mockDomain() 方法只是简单地利用底层 Groovy 语言的本机动态功能。(要了解 Groovy 中的元编程的更多信息,请查阅 。”)实际上,它会拦截通常存在于域类上的方法调用,并将它们替换为模拟行为,以进行测试。毫无疑问,这意味着不会模拟其他支持技术,比如条件块、Hibernate Query Language (HQL) 和 Query By Example (QBE)。如果您的代码依赖于这些技术中的任何一种,您需要编写集成测试并运行一个实际的数据库。

清单 14. 创建服务
1
2
3
4
$ grails create-service Admin

Created Service for Admin
Created Tests for Admin




毫无疑问,AdminService.groovy 文件会出现在 grails-app/services 目录中。如果查看 test/unit 目录,应该会看到一个名为 AdminServiceTests.groovy 的 GrailsUnitTestCase。
向 AdminService 添加一个假设性方法,仅允许 admin 角色中的用户重启服务器,如清单 15 所示:
清单 15. 将 restart() 方法添加到 AdminService
1
2
3
4
5
6
7
8
9
10
11
12
13
class AdminService {
  boolean transactional = true

  def restartServer(User user) {
    if(user.role == "admin"){
      //restart the server
      return true
    }else{
      log.info "Ha! ${user.name} thinks s/he is an admin..."
      return false
    }
  }
}




对此服务的测试非常简单。将 testRestartServer() 方法添加到 test/unit/AdminServiceTests.groovy,如清单 16 所示:
清单 16. 一个将会失败的服务测试
1
2
3
4
5
6
7
8
9
void testRestartServer() {
  def jdoe = new User(name:"John Doe", role:"user")
  def suziq = new User(name:"Suzi Q", role:"admin")

  //NOTE: no DI in unit tests
  def adminService = new AdminService()
  assertTrue adminService.restartServer(suziq)
  assertFalse adminService.restartServer(jdoe)
}




当在命令提示符处键入 grails test-app -unit AdminService 来运行此测试时,将会失败。就像最初的 User 测试运行一样,导致它失败的原因并不是您所期望的那样。看一下 HTML 报告,会发现熟悉的 No such property: log for class: AdminService 消息,如图 5 所示:
图 5. 依赖性注入的缺乏导致了单元测试失败但是,这次失败并不是因为域类上缺少元编程,而是因为缺少依赖性注入。具体来讲,所有 Grails 工件都会在运行时被注入一个 log 对象,以便它们可以轻松地记录消息,以供未来审核。
要注入一个模拟日志记录程序以供测试,将 AdminService 类封装到一个 mockLogging() 方法调用中,如清单 17 所示:
清单 17. 此测试将通过,这得益于 mockLogging()
1
2
3
4
5
6
7
8
9
void testRestartServer() {
  def jdoe = new User(name:"John Doe", role:"user")
  def suziq = new User(name:"Suzi Q", role:"admin")

  mockLogging(AdminService)
  def adminService = new AdminService()
  assertTrue adminService.restartServer(suziq)
  assertFalse adminService.restartServer(jdoe)
}




这一次,与预期的一样,测试通过了。所有日志输出都被发送到 System.out。请记住,您可以在 HTML 报告中看到此输出。
返回列表