code-x / xtest
简单的Magento测试框架
Requires
- php: >=5.3.0
Suggests
This package is not auto-updated.
Last update: 2020-01-24 15:46:00 UTC
README
简单的Magento测试框架。
通用
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,这样浏览器窗口就不会像正常模式下那样快速关闭。