当Dan用上了一位同事编写的小框架agiledox时,灵感闪现!这个框架其实很简单,它基于JUnit测试框架,根据测试类名和方法名,将每个测试方法都打印为类似文档的输出。程序员们意识到这个小玩具可以帮它们做一些文档性的工作,于是就开始用商业领域语法命名他们的类和方法,让agiledox产生的输出能直接被商业客户、分析师、测试人员都看懂!
<code class="language-java hljs has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;"><span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// CustomerLookup</span> <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// - finds customer by id</span> <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// - fails for duplicate customers</span> <span class="hljs-comment" style="color: rgb(136, 0, 0); box-sizing: border-box;">// - ...</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">public</span> <span class="hljs-class" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">class</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">CustomerLookupTest</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">extends</span> <span class="hljs-title" style="box-sizing: border-box; color: rgb(102, 0, 102);">TestCase</span> {</span> testFindsCustomerById() { ... } testFailsForDuplicateCustomers() { ... } ... }</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li></ul>
1.3 “Ubiquitous Language”
此时,恰逢Eric Evans发表了畅销书DDD(领域驱动设计),其中描述了为系统建模时,使用一种基于商业领域模型的Ubiquitous Language,让业务词汇渗透到代码中。于是,Dan决定定义一种分析师、测试人员、开发者、业务人员、用户都能懂的”Ubiquitous Language”。
<code class="language-gherkin hljs xml has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">Feature: <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">description</span>></span> As a <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">role</span>></span> I want <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">feature</span>></span> So that <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">business</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">value</span>></span> Scenario: <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">description</span>></span> Given <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">some</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">initial</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">context</span>></span>, When <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">an</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">event</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">occurs</span>></span>, Then <span class="hljs-tag" style="color: rgb(0, 102, 102); box-sizing: border-box;"><<span class="hljs-title" style="box-sizing: border-box; color: rgb(0, 0, 136);">ensure</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">some</span> <span class="hljs-attribute" style="box-sizing: border-box; color: rgb(102, 0, 102);">outcomes</span>></span>.</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li></ul>
就这样,BDD的雏形就出现了!但这种类似BRD的文档是如何与我们程序员的代码结合到一起的呢?下一节我们就详细分析一下。
2.三个核心概念
Feature、Scenario、Steps是BDD的三个核心概念,体现了BDD的三个重要价值:
- Living Document
- Executable Specification by Example(SbE)
- Automated Tests
2.1 Feature
Feature就像是文档一样,描述了功能特性、角色、以及 最重要的商业价值。
2.2 Scenario
场景就是上面提到的规范Specification。Cucumber提供了Scenario、Scenario Outline两种形式。使用时要注意,在Cucumber官博上的一篇文章“Are you doing BDD? Or are you just using Cucumber?”给出了一个反模式。
<code class="language-gherkin hljs applescript has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">Scenario Outline: Detect agent type based <span class="hljs-function_start" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">on</span></span> contract <span class="hljs-type" style="box-sizing: border-box;">number</span> (single contract found) Given I am <span class="hljs-function_start" style="box-sizing: border-box;"><span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">on</span></span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">the</span> <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"Find me"</span> page And I have entered a contract <span class="hljs-type" style="box-sizing: border-box;">number</span> When I click <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">the</span> <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"Continue"</span> button And a contract <span class="hljs-type" style="box-sizing: border-box;">number</span> match <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">is</span> found And <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">the</span> agent type <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">is</span> <DistributorType> Then <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">the</span> contract <span class="hljs-type" style="box-sizing: border-box;">number</span> field will become uneditable And <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">the</span> <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"Back"</span> button will be displayed And <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">the</span> following <<span class="hljs-type" style="box-sizing: border-box;">text</span>> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">and</span> <input field type> will be displayed Examples: | DistributorType | input field type | <span class="hljs-type" style="box-sizing: border-box;">text</span> | | Broker | Date <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">of</span> birth | Please enter your <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">last</span> <span class="hljs-property" style="box-sizing: border-box;">name</span> | | TiedAgent | Last <span class="hljs-property" style="box-sizing: border-box;">name</span> | Please enter your <span class="hljs-type" style="box-sizing: border-box;">date</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">of</span> birth |</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li><li style="box-sizing: border-box; padding: 0px 5px;">10</li><li style="box-sizing: border-box; padding: 0px 5px;">11</li><li style="box-sizing: border-box; padding: 0px 5px;">12</li><li style="box-sizing: border-box; padding: 0px 5px;">13</li><li style="box-sizing: border-box; padding: 0px 5px;">14</li></ul>
看出来了差别吧:Scenario Outline的核心依然应该是商业规则,而不能因为它对输入和输出的细化就将重点转移到UI界面。
<code class="language-gherkin hljs applescript has-numbering" style="display: block; padding: 0px; background-color: transparent; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-top-left-radius: 0px; border-top-right-radius: 0px; border-bottom-right-radius: 0px; border-bottom-left-radius: 0px; word-wrap: normal; background-position: initial initial; background-repeat: initial initial;">Scenario: Customer has a broker policy so DOB <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">is</span> requested Given I have a <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"Broker"</span> policy When I submit <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">my</span> policy <span class="hljs-type" style="box-sizing: border-box;">number</span> Then I should be asked <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">my</span> <span class="hljs-type" style="box-sizing: border-box;">date</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">of</span> birth Scenario: Customer has a tied agent policy so <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">last</span> <span class="hljs-property" style="box-sizing: border-box;">name</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">is</span> requested Given I have a <span class="hljs-string" style="color: rgb(0, 136, 0); box-sizing: border-box;">"TiedAgent"</span> policy When I submit <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">my</span> policy <span class="hljs-type" style="box-sizing: border-box;">number</span> Then I should be asked <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">for</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">my</span> <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">last</span> <span class="hljs-property" style="box-sizing: border-box;">name</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; background-color: rgb(238, 238, 238); top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right;"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li><li style="box-sizing: border-box; padding: 0px 5px;">3</li><li style="box-sizing: border-box; padding: 0px 5px;">4</li><li style="box-sizing: border-box; padding: 0px 5px;">5</li><li style="box-sizing: border-box; padding: 0px 5px;">6</li><li style="box-sizing: border-box; padding: 0px 5px;">7</li><li style="box-sizing: border-box; padding: 0px 5px;">8</li><li style="box-sizing: border-box; padding: 0px 5px;">9</li></ul>
2.3 Steps
Steps就是实际编码了,我们要在Java中实现出Feature文件中各种场景对应的代码,让它变成“活文档”!