Codeception,使用pageObject设计模式和Gherkin编写验收测试。

56
我正在寻找一个简单的代码示例,使用pageObject设计模式和gherkin语言,因为当我按照codeception BDD文档中的示例编写测试时,所有的示例都写在tests/support/AcceptanceTester.php文件中,我不明白(英语能力差- -)如何不把所有的代码都集中在AcceptanceTester.php文件中。
例如,我有一个带有两个按钮A和B的示例主页。如果用户点击按钮A,则加载页面A,否则如果用户点击按钮B,则加载页面B。
目前,我的AcceptanceTester:
<?php
// tests/_support/AcceptanceTester.php
/**
 * Inherited Methods
 * @method void wantToTest($text)
 * @method void wantTo($text)
 * @method void execute($callable)
 * @method void expectTo($prediction)
 * @method void expect($prediction)
 * @method void amGoingTo($argumentation)
 * @method void am($role)
 * @method void lookForwardTo($achieveValue)
 * @method void comment($description)
 * @method \Codeception\Lib\Friend haveFriend($name, $actorClass = NULL)
 *
 * @SuppressWarnings(PHPMD)
 */

class AcceptanceTester extends \Codeception\Actor
{
    use _generated\AcceptanceTesterActions;

    /**
     * @Given The home page
     */
    public function inHomePage()
    {
        $this->amOnPage("/");
        $this->seeInTitle('home');
    }

    /**
     * @When I click on the button A
     */
    public function goToThePageA()
    {
        $this->click(['name' => 'A']);
    }

    /**
     * @Then l go to the page A
     */
    public function ImInPageA()
    {
        $this->seeInTitle('page A');
    }

    /**
     * @When I click on the button B
     */
    public function goToThePageB()
    {
        $this->click(['name' => 'B']);
    }

    /**
     * @Then l go to the page B
     */
    public function ImInPageB()
    {
        $this->seeInTitle('page B');
    }
}

如果我运行命令'./vendor/bin/codecept run acceptance',一切都很顺利。但是正如我之前所说的,我需要学习如何不将所有代码集中在AcceptanceTester文件中。
因此,我创建了三个页面对象;一个用于主页,一个用于A页面,一个用于B页面。 代码:
主页对象:
<?php
// tests/_support/Page/PageHome.php
namespace Page;

class PageHome
{
    public static $URL = '/home';
    public static $title = "home";
    public static $aButton = ['name' => 'A'] ;
    public static $bButton = ['name' => 'B'] ;

    public static function route($param){
        return static::$URL.$param;
    }

    /**
     * @var \AcceptanceTester;
     */
    protected $acceptanceTester;

    public function __construct(\AcceptanceTester $I){
        $this->acceptanceTester = $I;
    }
}

{{A页面对象}}:
<?php
// tests/_support/Page/PageA.php
namespace Page;

class PageA
{
    public static $URL = '/home/pageA';
    public static $title = "page A";

    public static function route($param){
        return static::$URL.$param;
    }

    /**
     * @var \AcceptanceTester;
     */
    protected $acceptanceTester;

    public function __construct(\AcceptanceTester $I){
        $this->acceptanceTester = $I;
    }
}

还有B页面对象

<?php
// tests/_support/Page/PageB.php
namespace Page;

class PageB
{
    public static $URL = '/home/pageB';
    public static $title = "page B";

    public static function route($param){
        return static::$URL.$param;
    }

    /**
     * @var \AcceptanceTester;
     */
    protected $acceptanceTester;

    public function __construct(\AcceptanceTester $I){
        $this->acceptanceTester = $I;
    }
}

然后,我创建了三个步骤对象:homeChecker、goToPageA和goToPageB。 homeChecker步骤对象:
<?php
// tests/_support/Step/Acceptance/HomeChecker.php

namespace Step\Acceptance;
use Page\Acceotance\HomePage;

class HomeChecker extends \AcceptanceTester
{
    /**
     * @Given The home page
     */
    public function main()
    {
        $homePage = new PageHome($this);

        $this->amOnPage($homePage::URL);
        $this->checkTitle($homePage);
        $this->checkButtons($homePage);
    }

    private function checkTitle($homePage){
        $this->seeInTitle($homePage::$title);
    }

    private function checkButtons($homePage){
        $this->see($homePage::$aButton);
        $this->see($homePage::$bButton);
    }
}

PageAChecker stepObject

<?php
// tests/_support/Step/Acceptance/PageAChecker.php

namespace Step\Acceptance;
use Page\PageHome;
use Page\PageA;

class PageAChecker extends \AcceptanceTester
{
    /**
     * @When I click on the button A
     */
    public function clickButton()
    {
        $homePage = new PageHome($this);
        $this->click($homePage::$aButton);
    }

    /**
     * @Then l go to the page A
     */
    public function checkTitle()
    {
        $aPage = new PageA($this);
        $this->seeInTitle($aPage::$title);
    }

}

PageBChecker 步骤对象

<?php
// tests/_support/Step/Acceptance/PageBChecker.php

namespace Step\Acceptance;
use Page\PageHome;
use Page\PageB;

class PageBChecker extends \AcceptanceTester
{
    /**
     * @When I click on the button B
     */
    public function clickButton()
    {
        $homePage = new PageHome($this);
        $this->click($homePage::$bButton);
    }

    /**
     * @Then l go to the page B
     */
    public function checkTitle()
    {
        $bPage = new PageB($this);
        $this->seeInTitle($bPage::$title);
    }

}

现在,我不知道该做什么。如果我清空我的AcceptanceTester文件并再次运行'./vendor/bin/codecept run acceptance'命令,则测试是不完整的,并且我在shell中收到“未在上下文中找到”的警告:

enter image description here

我该怎么办?

更新 我在codeception的GitHub上创建了一个帖子,链接如下:

https://github.com/Codeception/Codeception/issues/5157

我描述了一个最小化的示例来重现我的问题和一个(非常)丑陋的解决方法。我希望找到一种好的方法并理解为什么我所描述的方法不起作用!


尝试将依赖项作为方法参数传递,例如 function clickButton($homePage PageHome) { $this->click($homePage::$bButton); } - Oliver Maksimovic
谢谢你的帮助 :) 我按照你说的改了,但是我得到了相同的输出...(“在上下文中找不到'I click on the button B'的步骤定义”) - spacecodeur
我相信你在函数上方的/**/注释中设置的消息正在被你的工具解析。你在那里放置了@符号,也许你不应该这样做? - Anatoliy Kim
我在创建我的stackoverflow帖子时遇到了这些消息:/ 求救我的救世主哈哈 - spacecodeur
这似乎很复杂。"页面对象"和"步骤对象"本质上不是一回事吗?两者都是用来简化你反复执行的操作的缩写符号。将它们合并起来只会让人感觉像是进入了兔子洞。测试应该易于理解,否则它的意义何在。 - John Dee
如果我只使用stepObject类,这段代码就无法工作。这实际上是Codeception的一个非常基本的用法:创建一个feature,创建一个stepObject类,将gherkin:snippets输出添加到stepObject中,然后我遇到了相同的问题(运行时找不到上下文)。 - spacecodeur
2个回答

1
我在shell中收到“未在上下文中找到”的警告。
好的,如何将gherkin文件执行与我自己的上下文类(PageObjects、StepObjects等)中定义的步骤链接起来?我们可以阅读Codeception文档中的章节“BDD > Configuration”:
正如我们之前提到的,步骤应该在上下文类中定义。默认情况下,所有的步骤都在Actor类(例如AcceptanceTester)中定义。但是,您可以包含更多的上下文。这可以在全局codeception.yml或套件配置文件中进行配置:
gherkin:
    contexts:
        default:
            - AcceptanceTester
            - AdditionalSteps
            - PageHome
            - HomeChekcer

这样,PageObjects、Helpers和StepObjects也可以成为上下文。

更好的方法

如果我们继续阅读:

但更可取的是按照它们的标签或角色包含上下文类。

这意味着,考虑到可扩展性和良好的组织,您不希望每个测试都超载所有页面对象。因此,您可以通过角色、标签或路径分配页面对象(或任何助手类)。请参阅文档中的下一段。根据您的示例,并按标签分配:

gherkin:
   contexts:
      default:
         - AcceptanceTester
      tag:
         myTagX:
             - Page\Acceotance\HomePage\HomeChecker
             - Page\PageHome
         anotherTag:
             - Page\Acceotance\another\AnotherChecker
             - Page\PageAnother

...并在Gherkin文件中:

@myTagX
Feature
(...)

0

我看到您正在尝试以可重用的方式编写自己的自动化层。您可以使用ScreenPlay模式。

页面对象和screenplay之间的区别在这里讨论。

关键区别在于screenplay模式组织页面对象。

John Ferguson的文章:ScreenPlay:自动接受测试的下一个阶段


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接