执行Behat
你已经定义好功能和场景了,你肯定希望看到Behat可以用了。在ls_project目录执行下面的命令:
$ ./vendor/behat/behat/bin/behat
如果一切正常运行,你可以看到这个:
$ behat 功能: ls 为了能看到的目录结构 作为一个UNIX用户 我需要能够列出当前目录的内容 场景: 列出目录中的2个文件 # features/ls.feature:7 假如 我在"test"目录中 并且 这个目录有"foo"文件 同时 这个目录有"bar"文件 当 我运行"ls"后 那么 我能看到 """ bar foo """ 1 scenario (1 undefined) 5 steps (5 undefined) 0m0.54s (9.96Mb) --- FeatureContext has missing steps. Define them with these snippets: /** * @Given 我在:arg1目录中 */ public function woZaiMuLuZhong($arg1) { throw new PendingException(); } /** * @Given 这个目录有:arg1文件 */ public function zheGeMuLuYouWenJian($arg1) { throw new PendingException(); } /** * @When 我运行:arg1后 */ public function woYunXingHou($arg1) { throw new PendingException(); } /** * @Then 我能看到 */ public function woNengKanDao(PyStringNode $string) { throw new PendingException(); } |
实现步骤定义
Behat会自动查找到features/ls.feature文件,并尝试把场景当成测试去执行。由于我们没有告诉Behat像“假如我在”test”目录中”这样的语句需要做什么,所以会出现上面的错误。Behat用正则表达式来匹配场景中的每一句话,并把这个正则表达式转换成每一个“步骤”。幸运的是,Behat可以自动打印出正则表达式,这样我们就不需要去创建每个步骤的定义:
/** * @Given 我在:arg1目录中 */ public function woZaiMuLuZhong($arg1) { throw new PendingException(); } 我们把Behat的建议添加到 features/bootstrap/FeatureContent.php中,重构参数$arg1为$dir: <?php use Behat\Behat\Context\Context; use Behat\Behat\Context\SnippetAcceptingContext; use Behat\Gherkin\Node\PyStringNode; use Behat\Gherkin\Node\TableNode; /** * Defines application features from the specific context. */ class FeatureContext implements Context, SnippetAcceptingContext { /** * @Given 我在:dir目录中 */ public function woZaiMuLuZhong($dir) { if (!file_exists($dir)) { mkdir($dir); } chdir($dir); } } |
我们使用Behat建议的正则表达式,引号中的值(如:”test”)作为$dir变量,在方法内部,我们简单的创建目录并进入到这个目录中。
以此类推,实现FeatureContent.php中剩下的3个步骤:
<?php use Behat\Behat\Context\Context; use Behat\Behat\Context\SnippetAcceptingContext; use Behat\Gherkin\Node\PyStringNode; use Behat\Gherkin\Node\TableNode; /** * Defines application features from the specific context. */ class FeatureContext implements Context, SnippetAcceptingContext { /** * @Given 我在:dir目录中 */ public function woZaiMuLuZhong($dir) { if (!file_exists($dir)) { mkdir($dir); } chdir($dir); } /** * @Given 这个目录有:file文件 */ public function zheGeMuLuYouWenJian($file) { touch($file); } /** * @When 我运行:command后 */ public function woYunXingHou($command) { exec($command, $output); $this->output = trim(implode("\n", $output)); } /** * @Then 我能看到 */ public function woNengKanDao(PyStringNode $string) { if ((string) $string !== $this->output) { throw new Exception( "Actual output is:\n" . $this->output ); } } } |
当指定多行的步骤参数——像我们上面的场景中使用的三个引号(”””),这个值传递到步骤函数(如:$string)时实际上是一个对象,使用(string)$string或者$string->getRaw()可以把它转换成字符串。
很好,现在你已经定义好所有的步骤,再次运行Behat:
$ ./vendor/behat/behat/bin/behat 功能: ls 为了能看到的目录结构 作为一个UNIX用户 我需要能够列出当前目录的内容 场景: 列出目录中的2个文件 # features/ls.feature:7 假如 我在"test"目录中 # FeatureContext::woZaiMuLuZhong() 并且 这个目录有"foo"文件 # FeatureContext::zheGeMuLuYouWenJian() 同时 这个目录有"bar"文件 # FeatureContext::zheGeMuLuYouWenJian() 当 我运行"ls"后 # FeatureContext::woYunXingHou() 那么 我能看到 # FeatureContext::woNengKanDao() """ bar foo """ 1 scenario (1 passed) 5 steps (5 passed) 0m0.56s (10.06Mb) |
成功了!Behat执行了所有步骤——创建新的目录,创建2个文件并且执行ls命令;然后对比实际结果和期望结果。
当然,你可以很容易地添加更多的场景和步骤。
Behat基础知识
当你运行 behat —init,它会把目录初始化成这样:
.
└── features
└── bootstrap
└── FeatureContext.php
2 directories, 1 file
任何和Behat有关的文件都在features目录,它由这三个基本区域:
features/ - Behat执行目录中所有的 *.feature文件
features/bootstrap - 在这个目录中所有的*.php文件在执行任何步骤前都会自动加载
features/bootstrap/FeatureContext.php - 这个文件是每个场景步骤被执行的上下文类
features基础知识
正如你所看到的,功能描述是一个简单的、可读的纯文本文件,使用Gherkin格式。每个功能描述文件都遵循这些基本规则:
每个*.feature文件通常只包含一个“功能”(如ls命令或者用户注册功能);
每个功能使用关键字“功能”开头,紧跟着是标题和三行缩进的功能定义;
一个功能通常会包含一序列场景,在第一个场景前的内容都当成是功能描述;
每个场景都以“场景”或“剧本”开头,关键字之后紧跟一个简短的场景描述;
场景有一序列的步骤,每个都以“假如”、“当”、“那么”、“但是”和“并且”这些关键字开头,对于Behat来说这些关键字是没有区别的,你应该把它们作用于一致的场景。
了解更多“ Writing Features - Gherkin Language ”
steps基础知识
在每个步骤中,你需要告诉Behat有“失败”发生的话,你需要抛出一个异常;Behat没有自己的断言工具,但你可以使用任何可以进行断言的工具(比如PHPUnit)。
如果没有任何异常或者失败的断言,Behat认为是“通过”的。
了解更多“ Defining Reusable Actions - Step Definitions ”
上下文类FeatureContent
Behat会为每个场景创建一个上下文对象,并在这个对象中执行所有的场景步骤。也就是说如果你需要在不同的步骤共享变量,你可以很简单地在上下文对象中使用属性。