4+1视图模型与c4模型_因此,如果您要将标签库用于视图模型,则必须对其进行测试,对吗?...

4+1视图模型与c4模型

在上一篇文章中,我介绍了关于(可视)组件的思考,并使用任务浏览器作为用户界面“组件”的示例。

Grails任务概述主屏幕线框

我解释说

  • 查看模型,例如保存相关数据的普通Groovy对象(PO​​GO),例如class TaskBrowser
  • 标记库( LayoutTagLib )和标记( def taskBrowser )以将关联HTML( views/layouts/components/_taskBrowser.gsp )呈现到页面

允许更多可维护和可测试的代码。

让我们把钱花在嘴边, 看看如何测试用过的标签库

零件

因此,这些是方程式中的(简化的)部分。

任务 –域类

class Task {
  enum Type { PERSONAL, WORK }

  String title
  Type type
}

TaskBrowser –只是一个带有数据的POGO

class TaskBrowser {

  List tasks = []

  /**
   * Month to start with.
   *
   * @return number between 1 and 12
   */
  int getStartMonth() {
    def nowDate = new Date()
    nowDate[Calendar.MONTH] + 1
  }
}

HomeController –在index操作中创建任务浏览器。

class HomeController {
  def taskService

  def index() {
    [taskBrowser: new TaskBrowser(tasks: taskService.retrieveTasks())]
  }
}

home / index.gspindex操作的GSP

<!doctype html>
<html>
 <head>
 <meta name="layout" content="main" />
 <title>Tasks</title>
 </head>

 <body>
 <g:taskBrowser taskBrowser="${taskBrowser}"/>
 </body>
</html>

views / layouts / components / _taskBrowser.gsp –任务浏览器HTML

<div class="row month-${startMonth}" id="task-browser">
<div class="six columns">
  <g:if test="${tasks}">
  <%-- 500 lines more... --%>

LayoutTagLib –最后,标签库

/**
 * Renders the task browser.
 * 
 * @attr taskBrowser REQUIRED a task browser instance
 * @attr filter Optionally a {@link Task.Type} to show only those tasks
 */
def taskBrowser = { attrs ->
  if (!attrs.taskBrowser) {
    throwTagError("Tag [taskBrowser] is missing " +
                    "required attribute [taskBrowser]")
  }

  TaskBrowser browser = attrs.taskBrowser

  // filter tasks by type
  def tasks = browser.tasks
  if (attrs.filter && attrs.filter instanceof Task.Type) {
    tasks = browser.tasks.findAll { task -> task.type == attrs.filter }
  } 

  out << render(template: '/layouts/components/taskBrowser', 
    model: [tasks : tasks,
      months : browser.months,
      startMonth : browser.startMonth
  ])
}

单元测试

如果您查看标记库代码,那么有一些有趣的事情足以涵盖单元测试。

通常,每当您使用Grails命令创建控制器,服务或标签库时,也会创建关联的单元测试。 如果没有,您以后可以随时创建一个

grails create-unit-test LayoutTagLib

无论如何,我们从LayoutTagLibSpec开始,它最初是很空的。

import grails.test.mixin.TestFor
import spock.lang.Specification

@TestFor(LayoutTagLib)
class LayoutTagLibSpec extends Specification {

 def setup() {
 }

 def cleanup() {
 }

 void "test something"() {
 expect:"fix me"
 true == false
 }
}

@TestFor批注是Grails框架的一部分。 它不仅指示被测类 (也就是我们应该在此处测试的实际单元),而且还为我们提供了该类的具体实例。

以后再说。

现在我们可以执行我们的第一个测试,称为…

任务浏览器标签应默认显示所有任务

尽管最基本的测试方法"test something"以“ test…”开始,但我尝试省略该部分。 我们显然是在创建测试,并且在前面重复“ test xxx”没有其他价值,但会占用空间。

如果我们正在TaskBrowser的单元测试中(例如TaskBrowserSpec ),我将从测试方法中跳过被测类的名称,例如“ 任务浏览器标签 应该显示……”。 因为我们使用的是更通用的LayoutTagLib所以我想知道讨论的是哪个标签-当然还有更多,所以我还是从“任务浏览器标签…”开始

我通常首先在测试方法中放置“ Given / When / Then Spock”​​标签。 这有助于我思考自己的头脑

  1. 有哪些先决条件? (给)
  2. 实际调用的代码是什么? (什么时候)
  3. 有什么要断言或验证的? (然后)

这是我现在拥有的:

@TestFor(LayoutTagLib)
class LayoutTagLibSpec extends Specification {

 void "task browser tag should show all tasks by default"() {

   given:

   when:

   then:
 }
}

标签的实际调用在When下。 由于@TestFor批注指向标记库类,因此我们可以使用隐式的tagLib变量来工作,该变量每次都引用一个干净的LayoutTagLib实例。

不要在单元测试中做到这一点,因为这是 - TestFor方式:

def layoutTagLib = new LayoutTagLib()
// or <span class="pl-k">def</span> layoutTagLib <span class="pl-k">=</span> applicationContext<span class="pl-k">.</span>getBean(Layout<span class="pl-k">TagLib</span>)
layoutTagLib.taskBrowser(...)

但请使用tagLib ,卢克。

tagLib.taskBrowser(...)

所以我们有这个:

@TestFor(LayoutTagLib)
class LayoutTagLibSpec extends Specification {

 void "task browser tag should show all tasks by default"() {

   given:

   when:
   tagLib.taskBrowser()

   then:
 }
}

我知道此测试的快乐路径流程需要一个TaskBrowser实例。 默认情况下,应该至少有一项任务来验证标签是否应显示该标签。 因此,让我们添加它们:

@TestFor(LayoutTagLib)
class LayoutTagLibSpec extends Specification {

 void "task browser tag should show all tasks by default"() {
 given:
 def task = new Task(title: "My task")
 def browser = new TaskBrowser(tasks: [task])

 when:
 tagLib.taskBrowser(taskBrowser: browser)

 then:
 true
 }
}

嘿,在那么块中为什么是true ? 这是因为我们需要在When之后有一个Then块,以便此时能够执行一次此测试。 通常我们会用Expect作为

@TestFor(LayoutTagLib)
class LayoutTagLibSpec extends Specification {

 void "task browser tag should show all tasks by default"() {

   given:

   expect:

 }
}

但我太懒了,无法将其更新为Expect并稍后将其更改回When / Then &#55357;&#56841; 我们需要后来就无妨。 Grails在标签库的单元测试中所做的实际上是使用提供的模型来渲染模板/layouts/components/_taskBrowser.gsp

还记得LayoutTagLib的代码吗?

/**
 * Renders the task browser.
 * 
 * @attr taskBrowser REQUIRED a task browser instance
 * @attr filter Optionally a {@link Task.Type} to show only those tasks
 */
def taskBrowser = { attrs ->
  if (!attrs.taskBrowser) {
    throwTagError("Tag [taskBrowser] is missing " +
                    "required attribute [taskBrowser]")
  }

  TaskBrowser browser = attrs.taskBrowser

  // filter tasks by type
  def tasks = browser.tasks
  if (attrs.filter && attrs.filter instanceof Task.Type) {
    tasks = browser.tasks.findAll { task -> task.type == attrs.filter }
  } 

  out << render(template: '/layouts/components/taskBrowser', 
    model: [tasks : tasks,
      months : browser.months,
      startMonth : browser.startMonth
  ])
}

如果上述(简单化的)测试成功,则_taskBrowser.gsp及其逻辑未因异常而失败。 您可以在模板中输入错误,然后查看评估失败。 仅涵盖GSP的评估可能值得测试,但是我们没有检查任何内容。

我们如何知道是否引用了正确的模板? 我们如何知道是否正确传递了正确的模型?

不可避免的真相

如果查看Grails文档的“ 测试”一章 ,您将看到一个测试SimpleTagLib响应的简单示例。

class SimpleTagLib {
  static namespace = 's'

  def hello = { attrs, body ->
    out << "Hello ${attrs.name ?: 'World'}"
  }
@TestFor(SimpleTagLib)
class SimpleTagLibSpec extends Specification {

  void "test tag calls"() {
    expect:
    tagLib.hello().toString() == 'Hello World'

我们的标签并不像返回“ Hello World”那样简单-我们的标签将500行复杂任务浏览器HTML呈现到输出缓冲区。 验证这种简单方法并不是那么简单。

这里有几种方法。

#1 –检查零件

通常, contains用于代码段中,只需检查项目是否存在。 我们可以尝试验证输出中某处是否存在单个任务“我的任务”,如下所示:

@TestFor(LayoutTagLib)
class LayoutTagLibSpec extends Specification {

 void "task browser tag should show all tasks by default"() {
   given:
   def task = new Task(title: "My task")
   def browser = new TaskBrowser(tasks: [task])

   when:
   def result = tagLib.taskBrowser(taskBrowser: browser).toString()

   then:
   result.contains "My task"
 }
}

我们已经成功验证了“我的任务”在500行输出中可见。

(请注意,在继续之前,请Swift加强测试–确保我们始终至少有多个测试项目来检查处理集合的逻辑,而不是单个项目)

@TestFor(LayoutTagLib)
class LayoutTagLibSpec extends Specification {

 void "task browser tag should show all tasks by default"() {
   given:
   def task1 = new Task(title: "My 1st task")
   def task2 = new Task(title: "My 2nd task")
   def browser = new TaskBrowser(tasks: [task1, task2])

   when:
   def result = tagLib.taskBrowser(taskBrowser: browser).toString()

   then:
   result.contains "My 1st task"
   result.contains "My 2nd task"
 }
}

缺点是我们将标记库逻辑 (显示或过滤任务)的测试与HTML呈现 (任务标题的存在)结合在一起

为了减轻这种情况,我们应该……

#2 –控制要渲染的部分

正如控制器的测试中,我们可以使用的功能ControllerUnitTestMixin 嘲笑用于渲染视图

使用隐式的getViews()getGroovyPages() ,它们返回一个Map供我们操作。 用我们自己的自定义内容覆盖实际模板,在其中我们控制模型的呈现方式和呈现方式。

首先,通过使测试失败,确保我们实际上覆盖了正确的模板路径。 taskBrowser标记表示render(template: '/layouts/components/taskBrowser'...因此我们必须将替代内容放在键'/layouts/components/_taskBrowser.gsp' —编写模板路径的格式存在差异。

@TestFor(LayoutTagLib)
class LayoutTagLibSpec extends Specification {

 void "task browser tag should show all tasks by default"() {
   given:
   def task1 = new Task(title: "My 1st task")
   def task2 = new Task(title: "My 2nd task")
   def browser = new TaskBrowser(tasks: [task1, task2])

   when:
   views['/layouts/components/_taskBrowser.gsp'] = 'bogus'
   def result = tagLib.taskBrowser(taskBrowser: browser).toString()

   then:
   result.contains "My 1st task"
   result.contains "My 2nd task"
 }
}

这正确地失败了…

Condition not satisfied:

result.contains "My 1st task"
|      |
bogus  false

…所以我们知道我们拥有正确的钥匙。

现在选择适当的内容。

只需打印任务集合即可为我们提供所需的一切,以验证现在我们的2个任务已传递给模型到我们的自定义模板。

Condition not satisfied:

result.contains "My 1st task"
|      |
|      false
[sample.Task : (unsaved), sample.Task : (unsaved)]

ang!

我们可以(也不应该)不依赖于Task类(我们的模型)的String表示形式。

不要添加toString()方法! 我们可以调整测试,以通过唯一属性(例如标题)检测是否存在正确的项目

@TestFor(LayoutTagLib)
class LayoutTagLibSpec extends Specification {

 void "task browser tag should show all tasks by default"() {
   given:
   def task1 = new Task(title: "My 1st task")
   def task2 = new Task(title: "My 2nd task")
   def browser = new TaskBrowser(tasks: [task1, task2])

   when:
   views['/layouts/components/_taskBrowser.gsp'] = '${tasks.title}'
   // make sure only titles are rendered e.g. [My 1st task, My 2nd task]
   def result = tagLib.taskBrowser(taskBrowser: browser).toString()

   then:
   result.contains "My 1st task"
   result.contains "My 2nd task"
 }
}

这样成功了!

而且很丑。 我们仍然依赖于将渲染结果( StreamCharBufferStreamCharBufferString ,需要将其整体与我们期望的内容进行比较或检查是否有部分内​​容。 对于测试一些小HTML代码段,这很好。

我将在以后的文章中分享一些技巧,因为该模型变得太复杂了,无法通过这种机制进行测试。 现在,我们将使用这种机制。

冲洗并重复

我经常将第一个简单的测试作为进一步测试的基础。 在第二项测试中,我们需要验证任务浏览器是否实际上可以针对诸如PersonalWork之类的任务类型进行一次过滤。

通过将filter属性传递给标签来引入过滤filter 。 生成的测试可能如下所示。 我们正在检查的是仅呈现具有该类型的一项任务,而不呈现另一项。

void "task browser tag should show only personal tasks"() {
 given:
 def task1 = new Task(title: "My 1st task", type: Type.PERSONAL)
 def task2 = new Task(title: "My 2nd task", type: Type.WORK)
 def browser = new TaskBrowser(tasks: [task1, task2])

 and: 
 def filterType = Type.PERSONAL

 when:
 views['/layouts/components/_taskBrowser.gsp'] = '${tasks.title}'
 def result = tagLib.taskBrowser(taskBrowser: browser, 
 filter: filterType).toString()

 then:
 result.contains "My 1st task"
 !result.contains("My 2nd task")
}

(通常,我会给given:when:等块提供各种标签,但我将其留给读者练习。)

如果我不厌倦与罗马人和角斗士度过一个周末,我会写一些更多的变化和结尾部分,但是我希望只是在Ted Vinke Blog的上方写下对某些人有帮助。

测试愉快!

翻译自: https://www.javacodegeeks.com/2016/06/youre-using-tag-libraries-view-models-test-right.html

4+1视图模型与c4模型

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值