code-x/xtest

此包已被废弃且不再维护。未建议替代包。

简单的Magento测试框架

安装次数: 1,874

依赖者: 0

建议者: 1

安全: 0

星标: 53

关注者: 13

分支: 14

开放问题: 1

类型:magento-module

0.9.0.0 2015-09-16 14:38 UTC

This package is not auto-updated.

Last update: 2020-01-24 15:46:00 UTC


README

简单的Magento测试框架。

Build Status

http://xtest-mage.com/

通用

xtest是一个集成PHPUnit到Magento的测试套件。它支持基本的单元测试以及Selenium测试。

xtest旨在为项目创建集成测试。我们使用预配置的数据库,而不是创建所有实体。

安装

要安装xtest,将所有文件链接到您的magento安装。我们提供了一个modman文件来自动完成此操作。

单元测试

单元测试在当前数据库上运行。所有更改都在事务中运行,因此不会更改您的数据库。(除了不支持的数据库回滚的MyISAM表)

开始使用

所有示例均来自https://github.com/code-x/xTest.Demo。我们还提供了一个演示视频https://www.youtube.com/watch?v=rPyhS_neY6k

要开始测试,在您正在工作的自定义模块中创建一个测试类。您只需要创建一个名为Test的目录。然后创建一个包含以下内容的文件HomepageControllerTest.php

文件: app/code/local/Codex/Demo/Test/Controller/HomepageControllerTest.php

<?php

class Codex_Demo_Test_Controller_HomepageControllerTest extends Codex_Xtest_Xtest_Unit_Frontend
{

    /**
     *
     */
    public function testHomePageContainsNewProducts()
    {
        $this->dispatch('/');

        // Checks Layout Wrapper exists
        $this->assertLayoutBlockExists('cms.wrapper');

        // Checks page contains some content
        $this->assertContains('New Products', $this->getResponseBody());

    }

}

使用以下命令运行测试

cd htdocs/tests
php phpunit.phar ../app/code/local/Codex/Demo/

恭喜!您已使用xtest完成了第一个单元测试。

您不必直接调用测试,在shell中没有特定文件名的情况下,所有目录中的文件都会传递,结果打印在屏幕上。要调用特定的测试用例,只需添加文件名即可

cd htdocs/tests
php phpunit.phar ../app/code/local/Codex/Demo/Test/Controller/HomepageControllerTest.php

参数

--store_code - 启动在前端测试上运行的商店代码 --external -- 运行有@external注解的测试 --disable_double - 不使用双重模拟

基于Selenium

--browser - 设置selenium测试中使用的浏览器(必须可用) --breakpoints - 定义响应式断点,例如“1024x800,1280x1024” --debug -- 在异常发生时不要关闭浏览器窗口

配置

要设置固定和selenium配置(例如产品SKU,电子邮件地址等),请参阅app/code/community/Codex/Xtest/etc/xtest.xml。您可以通过在模块中创建自己的xtest.xml并使用自己的值覆盖我们的值来更改所有值。

对于magento示例数据,您可以使用此数据

<config>
    <default>
        <xtest>
            <selenium>
                <screenshot>
                    <breakpoints>450x1024,1280x1024</breakpoints>
                </screenshot>
            </selenium>

            <fixtures>

                <customer>
                    <email>devnull@code-x.de</email>
                    <firstname>Test Vorname</firstname>
                    <lastname>Test Nachname</lastname>

                    <billing_address>
                        <firstname>Xtest Firstname</firstname>
                        <lastname>Xtest Lastname</lastname>
                        <street>Xtest Street</street>
                        <city>Xtest City</city>
                        <postcode>33100</postcode>
                        <telephone>Xtest Phone</telephone>
                        <country_id>DE</country_id>
                        <region_id>88</region_id>
                    </billing_address>

                    <shipping_address>
                        <firstname>Xtest Firstname</firstname>
                        <lastname>Xtest Lastname</lastname>
                        <street>Xtest Street</street>
                        <city>Xtest City</city>
                        <postcode>33100</postcode>
                        <telephone>Xtest Phone</telephone>
                        <country_id>DE</country_id>
                        <region_id>88</region_id>
                    </shipping_address>

                </customer>

                <order>

                    <customer_id>0</customer_id>
                    <customer_data>
                        <email>devnull@code-x.de</email>
                        <firstname>Test Firstname</firstname>
                        <lastname>Test Lastname</lastname>
                    </customer_data>

                    <payment_method>
                        <method>cashondelivery</method>
                        <!-- some other options-->
                    </payment_method>

                    <shipping_method>
                        <method>ups_XPD</method>
                    </shipping_method>

                    <billing_address>
                        <firstname>Xtest Firstname</firstname>
                        <lastname>Xtest Lastname</lastname>
                        <street>Xtest Street</street>
                        <city>Xtest City</city>
                        <postcode>33100</postcode>
                        <telephone>Xtest Phone</telephone>
                        <country_id>DE</country_id>
                        <region_id>88</region_id>
                    </billing_address>

                    <shipping_address>
                        <firstname>Xtest Firstname</firstname>
                        <lastname>Xtest Lastname</lastname>
                        <street>Xtest Street</street>
                        <city>Xtest City</city>
                        <postcode>33100</postcode>
                        <telephone>Xtest Phone</telephone>
                        <country_id>DE</country_id>
                        <region_id>88</region_id>
                    </shipping_address>

                    <items>

                        <item>
                            <qty>1</qty>
                            <sku>abl004</sku>
                        </item>

                    </items>

                </order>
            </fixtures>

            <selenium>

                <checkout>

                    <customer>
                        <email>selenium@code-x.de</email>
                        <firstname>Firstname</firstname>
                        <lastname>Lasntname</lastname>
                    </customer>

                    <addtocart>

                        <product_1>
                            <sku>abl004</sku>
                            <qty>1</qty>
                        </product_1>

                    </addtocart>

                    <billing_address>

                        <firstname>test firstname</firstname>
                        <lastname>test lastname</lastname>
                        <company>company</company>
                        <telephone>123456</telephone>
                        <street1>Teststreet 32</street1>
                        <city>Testcity</city>
                        <postcode>33100</postcode>
                        <use_for_shipping_no>1</use_for_shipping_no>

                    </billing_address>

                    <shipping_address>
                        <!-- todo -->
                    </shipping_address>

                    <shipping_method>
                        <method>ups_XPD</method>
                    </shipping_method>

                    <payment_method>
                        <method>cashondelivery</method>
                    </payment_method>

                </checkout>

            </selenium>

        </xtest>
    </default>
</config>

基本测试类

我们提供两个不同的单元测试类。请扩展Codex_Xtest_Xtest_Unit_Frontend以创建前端测试,并扩展Codex_Xtest_Xtest_Unit_Admin以创建有关Magento后端的测试。

Codex_Xtest_Xtest_Unit_Abstract

  • dispatchUrl($httpUrl, $postData = null):派发URL

  • dispatch($route, $params = array(), $postData = null):派发magento URL

  • dispatch($route, $params = array(), $postData = null): 分发 Magento URL

Codex_Xtest_Xtest_Unit_Frontend

  • assertPaymentMethodIsAvailable: 检查支付方式是否可用
  • populuateQuote: 将 Magento 采购单填充到所有 Magento 方法中
  • setCustomerAsLoggedIn: 设置客户为已登录

Codex_Xtest_Xtest_Unit_Admin

自动将第一个管理员用户设置为已登录。

模拟

模拟模型或辅助工具是创建测试的基本功能。通常,你应该测试一个明确的函数,并模拟所有其他依赖项以获得可靠的结果。

模型模拟

在这个例子中,我们有一个模型 catalog/product,它有一个方法 isSaleable。此方法依赖于 isAvailable 方法,当产品可用时返回 true,不可用时返回 false。

那么,让我们开始模拟。

<?php

class Codex_Demo_Test_Model_ProductTest extends Codex_Xtest_Xtest_Unit_Frontend
{

    public function demoProvider()
    {
        return array(
            array( true, true ),
            array( false, false )
        );
    }

    /**
     * As catalog/product Model
     * - when product is not available
     * - then it should not be saleable
     *
     * @dataProvider demoProvider
     **/
    public function testDemoMock($productIsAvailable, $expectedSaleable)
    {
        $mock = $this->getModelMock('catalog/product', array('isAvailable') );
        $mock->expects($this->any())
            ->method('isAvailable')
            ->willReturn( $productIsAvailable );
        $this->addModelMock( 'catalog/product', $mock );

        /** @var Mage_Catalog_Model_Product $product */
        $product = Mage::getModel('catalog/product');
        $this->assertEquals( $product->isSalable(), $expectedSaleable );
    }

}

通常,你不会测试你的模拟结果。相反,你必须模拟依赖模型来测试你的模型。这里我们创建了一个简单的模拟示例。

辅助工具模拟

你可以用相同的方式模拟辅助工具

$mock = $this->getHelperMock('codex_demo', array('getDemoMethod') );
$mock->expects( $this->any() )
    ->method( 'getDemoMethod' )
    ->willReturn( 'some value' );
$this->addHelperMock('codex_demo', $mock);

请考虑使用 $this->getHelperMock() 创建模拟,并使用 $this->addHelperMock() 发布你的模拟。

永久模拟:双重模拟

有时你有一个与外部服务通信的类,或者你的类正在做非常疯狂的事情,所以你不能测试其他模块。在这种情况下,你可以创建一个永久模拟。

<?php

class Codex_Demo_Model_Weather extends Varien_Object
{

    protected function _apiCall($city, $country)
    {
        return file_get_contents('http://api.openweathermap.org/data/2.5/weather?q='.$city.','.$country);
    }

    /**
     * Return weather for city eg. "broken clouds"
     *
     * @param $city
     * @param $country_id
     * @return mixed
     */
    public function getWeather( $city, $country_id )
    {
        $data = json_decode( $this->_apiCall( $city, $country_id ), true );
        return $data['weather'][0]['description'];
    }

}

如果你创建了第二个类

class Codex_Demo_Test_Double_Model_Weather extends Codex_Demo_Model_Weather
{
    protected function _apiCall($city, $country)
    {
        return '{ "coord": { "lon": 8.75, "lat": 51.72 }, "sys": { "type": 3, "id": 177301, "message": 0.0283, "country": "DE", "sunrise": 1425190178, "sunset": 1425229494 }, "weather": [ { "id": 803, "main": "Clouds", "description": "broken clouds", "icon": "04d" } ], "base": "cmc stations", "main": { "temp": 283.41, "humidity": 65, "pressure": 1001, "temp_min": 283.15, "temp_max": 283.55 }, "wind": { "speed": 1, "gust": 3, "deg": 180 }, "rain": { "3h": 0 }, "clouds": { "all": 64 }, "dt": 1425221237, "id": 2855745, "name": "Paderborn", "cod": 200 }';
    }

}

xtest 将使用 Codex_Demo_Test_Double_Model_Weather 而不是 Codex_Demo_Model_Weather。这很有帮助,因为你的测试不依赖于外部服务。

块模拟

块应该从模型获取所有数据,所以你真的不想模拟它们。

固定装置

我们不喜欢 yaml。所以我们使用 Magento 类生成测试数据。通常我们使用预配置的数据库,这样我们就不需要在测试之前创建所有产品数据。

订单 / 采购单

这将创建一个基本的测试订单。

/** @var $orderFixture Codex_Xtest_Xtest_Fixture_Order */
$orderFixture = Xtest::getXtest('xtest/fixture_order');
$testOrder = $orderFixture->getTest();

客户

这将创建一个测试客户。

/** @var $customerFixture Codex_Xtest_Xtest_Fixture_Customer */
$customerFixture = Xtest::getXtest('xtest/fixture_customer');
$testCustomer = $customerFixture->getTest()

邮件

在您使用 Xtest 的整个工作中,所有邮件都不会被发送;它们都会排队,可以进行全面控制。您可以通过这种方式访问邮件队列

/** @var $mailqueue Codex_Xtest_Xtest_Helper_Mailqueue */
$mailqueue = Xtest::getXtest('xtest/helper_mailqueue');

print_r( $mailqueue->getQueue() );

此外,Xtest 还提供了一些有用的断言

  • $this->assertMailTemplateIdSent( $yourTemplateId );
  • $this->assertMailsSent( $yourMailsSentCount )

示例

创建订单、发送订单电子邮件并检查是否发送邮件

<?php

class Codex_Demo_Test_Integration_OrderTest extends Codex_Xtest_Xtest_Unit_Frontend
{

    /**
     * As Customer
     * - when i plaved a order
     * - then I should reveice a new order email
     */
    public function testOrderMail()
    {
        /** @var $orderFixture Codex_Xtest_Xtest_Fixture_Order */
        $orderFixture = Xtest::getXtest('xtest/fixture_order');
        $testOrder = $orderFixture->getTest();

        $testOrder->sendNewOrderEmail();

        $this->assertMailsSent( 1 );
        $this->assertMailTemplateIdSent( 'sales_email_order_template' );
    }

}

分发路由、检查布局是否存在

<?php

class Codex_Demo_Test_Controller_HomepageControllerTest extends Codex_Xtest_Xtest_Unit_Frontend
{

    /**
     * As Customer
     * - when I open Homepage
     * - I should see "New Products"
     */
    public function testHomePageContainsNewProducts()
    {
        $this->dispatch('/');

        // Checks Layout Wrapper exists
        $this->assertLayoutBlockExists('cms.wrapper');

        // Checks page contains some content
        $this->assertContains('New Products', $this->getResponseBody() );

    }

}

渲染HTML

当selenium-server运行时,您可以选择对html进行截图。这些截图以png格式存储在您的项目目录中。

class Codex_Demo_Test_Selenium_HomepageScreenshotTest extends Codex_Xtest_Xtest_Unit_Frontend
{

    public function testRenderHomepage()
    {
        $this->dispatch('/');
        $this->renderHtml('homePage', $this->getResponseBody() );
    }

}

从客户/账户中截图非常方便,因为您可以模拟或创建一些数据。在这里创建的所有数据在测试结束后都会被还原,因为您正在扩展Codex_Xtest_Xtest_Unit_Abstract

class Codex_Demo_Test_Selenium_CustomerAccountScreenshotTest extends Codex_Xtest_Xtest_Unit_Frontend {
    public function testOrderHistory()
    {
        /** @var $customerFixture Codex_Xtest_Xtest_Fixture_Customer */
        $customerFixture = Xtest::getXtest('xtest/fixture_customer');
        $customer = $customerFixture->getTest();
        $customer->setConfirmation(null);
        $customer->save();

        /** @var $orderFixture Codex_Xtest_Xtest_Fixture_Order */
        $orderFixture = Xtest::getXtest('xtest/fixture_order');

        $quote = $orderFixture->getFixtureQuote()->getTest( $customer );
        $order = $orderFixture->convertQuoteToOrder( $quote );
        $order->setState( current( Mage::getSingleton('sales/order_config')->getVisibleOnFrontStates() ) );
        $order->save();

        $this->setCustomerAsLoggedIn( $customer );

        $this->dispatch('sales/order/history');
        $this->renderHtml( 'account order history', $this->getResponseBody() );

        $this->dispatch('sales/order/view/order_id/'.$order->getId());
        $this->renderHtml( 'account order details', $this->getResponseBody() );
    }
}

您可以通过浏览到https:///YourProject/htdocs/tests/view/来查看截图(和测试结果)

Selenium测试

所有Selenium测试都是针对您的当前数据库运行的。什么都不能还原。您必须自己清理数据!(或者您不在乎清理)请确保您正在扩展Codex_Xtest_Xtest_Selenium_TestCase

使用Selenium

您必须首先启动Selenium。我们提供了所有必要的文件在htdocs/tests/selenium中。只需运行start.sh即可启动它。

cd htdocs/tests/selenium
./start.sh

页面对象

我们提供了一些基本的页面对象来简化处理Selenium测试。让我们从一些非常棘手的测试开始:单页结账进度。

<?php

class Codex_Demo_Test_Selenium_CheckoutTest extends Codex_Xtest_Xtest_Selenium_TestCase
{

    protected static $_customerEmail;
    protected static $_customerPassword;

    public function setUp()
    {
        parent::setUp();

        $customerConfig = self::getSeleniumConfig('checkout/customer');
        self::$_customerEmail = $customerConfig['email'];

        // Delete Testcustomer
        $customerCol = Mage::getModel('customer/customer')->getCollection();
        $customerCol->addFieldToFilter('email', self::$_customerEmail );
        $customerCol->walk('delete');

        // Create a new one
        $customer = Mage::getModel('customer/customer');
        $customer->setData($customerConfig);
        self::$_customerPassword = $customer->generatePassword();
        $customer->setStore( current( Mage::app()->getStores() ) ); // TODO
        $customer->setPassword( self::$_customerPassword );
        $customer->validate();
        $customer->setConfirmation(null);
        $customer->save();

        $customer->load( $customer->getId() );
        $customer->setConfirmation(null);
        $customer->save();

        $_custom_address = array (
            'firstname' => 'Test',
            'lastname' => 'Test',
            'street' => array (
                '0' => 'Sample address part1',
            ),
            'city' => 'Paderborn',
            'region_id' => '',
            'region' => '88',
            'postcode' => '33100',
            'country_id' => 'DE',
            'telephone' => '0000111',
        );
        $customAddress = Mage::getModel('customer/address');
        $customAddress->setData($_custom_address)
            ->setCustomerId($customer->getId())
            ->setIsDefaultBilling('1')
            ->setIsDefaultShipping('1')
            ->setSaveInAddressBook('1');
        $customAddress->save();
    }

    public function testOnepageCheckout()
    {

        $cartConfig = $this->getSeleniumConfig('checkout/addtocart');
        foreach( $cartConfig AS $_productData )
        {

            /** @var $productPageObject Codex_Xtest_Xtest_Pageobject_Frontend_Product */
            $productPageObject = $this->getPageObject('xtest/pageobject_frontend_product');

            $productPageObject->openBySku( $_productData['sku'] );
            $productPageObject->setQty( $_productData['qty'] );

            $productPageObject->pressAddToCart();
            $productPageObject->assertAddToCartMessageAppears();

        }

        /** @var $cartPageObject Codex_Xtest_Xtest_Pageobject_Frontend_Cart */
        $cartPageObject = $this->getPageObject('xtest/pageobject_frontend_cart');
        $cartPageObject->open();

        $cartPageObject->takeResponsiveScreenshots('products in cart');

        $this->assertEquals( count($cartConfig), count( $cartPageObject->getItems() ), 'cart is missing some items' );

        $cartPageObject->clickProceedCheckout();
        $this->assertContains('checkout/onepage/', $this->url() );

        // ---

        /** @var $checkoutPageObject Codex_Xtest_Xtest_Pageobject_Frontend_Checkout */
        $checkoutPageObject = $this->getPageObject('xtest/pageobject_frontend_checkout');

        $checkoutPageObject->takeResponsiveScreenshots('login');
        $checkoutPageObject->login( self::$_customerEmail, self::$_customerPassword );
        $checkoutPageObject->assertStepIsActive('billing');

        // ---

        $checkoutPageObject->setBillingAddress();
        $checkoutPageObject->takeResponsiveScreenshots('billing address');
        $checkoutPageObject->nextStep();

        // ---

        // TODO: Shipping Address

        // ---

        $checkoutPageObject->assertStepIsActive('shipping_method');
        $checkoutPageObject->setShippingMethod();
        $checkoutPageObject->takeResponsiveScreenshots('shipping method');
        $checkoutPageObject->nextStep();

        // ---

        $checkoutPageObject->assertStepIsActive('payment');
        $checkoutPageObject->setPaymentMethod();
        $checkoutPageObject->takeResponsiveScreenshots('payment method');
        $checkoutPageObject->nextStep();

        // ---

        $checkoutPageObject->assertStepIsActive('review');
        $checkoutPageObject->acceptAgreements();
        $checkoutPageObject->takeResponsiveScreenshots('review');
        $checkoutPageObject->nextStep();

        // ---

        $checkoutPageObject->takeResponsiveScreenshots();
        $checkoutPageObject->assertIsSuccessPage();

    }

}

确保您已经按照“配置”章节中所述配置了测试数据。

运行Selenium测试

要使用firefox运行测试并生成450px和1280px宽度的截图,请打开控制台并输入

cd htdocs/tests
php phpunit.phar ../app/code/local/Codex/Demo/Test/Selenium/CheckoutTest.php --browser firefox --breakpoints 450x800,1280x1024

提示:如果您正在调试测试,可以使用参数--debug,这样浏览器窗口就不会像正常模式下那样快速关闭。