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

精通 Grails 测试 Grails 应用程序(3)

精通 Grails 测试 Grails 应用程序(3)

对比单元测试和集成测试如我前面所述,Grails 支持两种基本类型的测试:单元测试和集成测试。这两者之间没有语法区别 — 它们都是用相同的断言写的 GroovyTestCase。它们的区别在于语义。单元测试孤立地测试类,而集成测试在一个完整的运行环境中测试类。
坦白地说,如果您想将所有的 Grails 测试都编写成集成测试,则刚好符合我的想法。所有 Grails create-* 命令都生成相应的集成测试,所以很多人都使用现成的集成测试。正如稍后看到的一样,很多测试需要在完整的运行环境中进行,因此默认使用集成测试是很好的选择。
如果您想测试一些非核心 Grails 类,则适合使用单元测试。要创建一个单元测试,请输入 grails create-unit-test MyTestUnit。因为测试脚本不是在不同的包中创建的,所以单元测试和集成测试的名称应该是惟一的。如果不是这样的话,将会收到清单 7 所示的错误消息:
清单 7. 单元测试和集成测试同名时收到的错误消息
1
2
3
4
5
6
7
8
9
The sources
/src/trip-planner2/test/integration/HotelStayTests.groovy and
   /src/trip-planner2/test/unit/HotelStayTests.groovy are
   containing both a class of the name HotelStayTests.
@ line 3, column 1.
   class HotelStayTests extends GroovyTestCase {
   ^

1 error




因为集成测试默认使用后缀 Tests,所以我在所有单元测试上都使用后缀 UnitTests,避免混淆。
为简单的验证错误消息编写测试下一个用户场景说明 hotel 字段不能留空。这很容易通过内置的 Grails 验证框架来实现。将一个 static constraints 块添加到 HotelStay,如清单 8 所示:
清单 8. 将一个 static constraints 块添加到 HotelStay
1
2
3
4
5
6
7
8
9
10
11
12
13
class HotelStay {
  static constraints = {
    hotel(blank:false)
    checkIn()
    checkOut()
  }
   
  String hotel
  Date checkIn
  Date checkOut
   
  //the rest of the class remains the same
}




输入 grails run-app。如果您尝试在留空 hotel 字段的情况下创建一个 HotelStay,将收到如图 6 所示的错误消息:
图 6. 空字段的默认错误消息我敢保证您的用户会喜欢这个特性,但对默认的错误消息还不是很满意。假设他们稍微改动了一下用户场景:hotel 字段不能留空;如果留空,错误消息会提示 “Please provide a hotel name”。
现在您已经添加了一些定制代码 — 尽管它就像一个定制的 String 那么简单 — 接下来应该添加测试了(当然,编写一个验证用户场景的完整性的测试 — 尽管不涉及到定制代码 — 也是完全可以接受的。
打开 grails-app/i18n/messages.properties 并添加 hotelStay.hotel.blank=Please provide a hotel name。尝试在浏览器中提交一个空 hotel。这时您将看到自己的定制消息,如图 7 所示:
图 7. 显示定制的验证错误消息向 HotelStayTests.groovy 添加一个新测试,检验对空字段的验证是否有效,如清单 9 所示:
清单 9. 测试验证错误
1
2
3
4
5
6
7
8
9
class HotelStayTests extends GroovyTestCase {
  void testBlankHotel(){
    def h = new HotelStay(hotel:"")
    assertFalse "there should be errors", h.validate()
    assertTrue "another way to check for errors after you call validate()", h.hasErrors()
  }

  //the rest of the tests remain unchanged
}




在生成的控制器中,您已经看到添加到域类中的 save() 方法。在这里,我本来也可以调用 save(),但事实上我并不想把新的类保存到数据库。我只关注验证是否发生。由 validate() 方法来完成这个任务。如果验证失败,则返回 false。如验证成功,则返回 true。
hasErrors() 是另一个很有价值的测试方法。在调用 save() 或 validate() 之后,hasErrors() 允许您查看验证错误。
清单 10 是经过扩展的 testBlankHotel(),它引入了其他一些很有用的验证方法:
清单 10. 验证错误的高级测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class HotelStayTests extends GroovyTestCase {
  void testBlankHotel(){
   def h = new HotelStay(hotel:"")
   assertFalse "there should be errors", h.validate()
   assertTrue "another way to check for errors after you call validate()", h.hasErrors()  
   
   println "\nErrors:"
   println h.errors ?: "no errors found"   
      
   def badField = h.errors.getFieldError('hotel')
   println "\nBadField:"
   println badField ?: "hotel wasn't a bad field"
   assertNotNull "I'm expecting to find an error on the hotel field", badField


   def code = badField?.codes.find {it == 'hotelStay.hotel.blank'}
   println "\nCode:"
   println code ?: "the blank hotel code wasn't found"
   assertNotNull "the blank hotel field should be the culprit", code
  }
}




确定类没有通过验证之后,您可以调用 getErrors() 方法(在这里,借助 Groovy 简洁的 getter 语法,它被缩略为 errors),返回一个 org.springframework.validation.BeanPropertyBindingResult。就像 GORM 与 Hibernate 相比是一个瘦 Groovy 层一样,Grails 验证只不过是一个简单的 Spring 验证。
调用 println 的结果不会在命令行上显示,但它们出现在 HTML 报告中,如图 8 所示:
图 8. 查看测试的 println 输出在 HotelStayTests 报告的右下角单击 System.out 链接。
中给人亲切感觉的 Elvis 操作符(转过脸来 — 看见他向后梳起的发型和那双眼睛吗?)是一个缩略的 Groovy 三元操作符。如果 ?: 左边的对象为 null,将使用右边的值。
将 hotel 字段更改为 "Holiday Inn" 并重新运行测试。您将在 HTML 报告中看到另一个 Elvis 输出,如图 9 所示:
图 9. 测试输出中的 Elvis看见 Elvis 之后,不要忘记清空 hotel 字段 — 如果您不希望留下中断的测试的话。
如果仍然显示关于 checkIn 和 checkOut 的验证错误,您不必担心。就这个测试而言,您完全可以忽略它们。但是这表明您不应该仅测试错误是否出现 — 您应该确保特定的 错误被抛出。
注意,我没有断言定制错误消息的确切文本。为什么我上一次关注匹配的字符串(测试 toString 的输出时)而这一次没有关注?toString 方法的定制输出便是上一个测试的目的。这一次,我更关心的是确定验证代码的执行,而不是 Grails 是否正确呈现消息。这表明测试更像一门艺术,而不是科学(如果我想验证准确的消息输出,则应该使用 Web 层测试工具,比如 Canoo WebTest 或 ThoughtWorks Selenium)。
返回列表