🧪 Testing de modulos — PHPUnit + Behat en PrestaShop
Actualizado: 2025-01-15
#Tipos de tests en PS
| Tipo | Herramienta | Que testea | Velocidad |
|---|---|---|---|
| Unit | PHPUnit | Logica pura sin dependencias (calculos, validaciones) | Muy rapido |
| Integration | PHPUnit + BD | Interaccion con BD, ObjectModel, Configuration | Medio |
| Functional | Behat + Selenium | Flujos completos: install, config, front hooks | Lento |
#PHPUnit — configuracion
Estructura de tests en el modulo
text
modules/ecom_mimodulo/
├── tests/
│ ├── phpunit.xml # Configuracion PHPUnit
│ ├── bootstrap.php # Bootstrap para tests
│ ├── Unit/
│ │ ├── PriceCalculatorTest.php
│ │ └── ValidatorTest.php
│ ├── Integration/
│ │ ├── InstallTest.php
│ │ └── ConfigurationTest.php
│ └── Fixtures/
│ └── products.json
├── composer.json
└── ...
tests/phpunit.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd"
bootstrap="bootstrap.php"
colors="true"
verbose="true">
<testsuites>
<testsuite name="Unit">
<directory>Unit</directory>
</testsuite>
<testsuite name="Integration">
<directory>Integration</directory>
</testsuite>
</testsuites>
<coverage>
<include>
<directory suffix=".php">../classes</directory>
<directory suffix=".php">../src</directory>
</include>
</coverage>
<php>
<env name="PS_ROOT" value="/var/www/prestashop" />
</php>
</phpunit>
tests/bootstrap.php
php
<?php
/**
* Bootstrap para tests del modulo
* Carga PrestaShop core para integration tests
*/
$psRoot = getenv('PS_ROOT') ?: '/var/www/prestashop';
// Para unit tests puros, no necesitamos PS core
if (getenv('UNIT_ONLY')) {
// Autoloader del modulo
require_once dirname(__DIR__) . '/vendor/autoload.php';
return;
}
// Para integration tests, cargar PS
if (file_exists($psRoot . '/config/config.inc.php')) {
require_once $psRoot . '/config/config.inc.php';
}
// Cargar clases del modulo
require_once dirname(__DIR__) . '/ecom_mimodulo.php';
if (file_exists(dirname(__DIR__) . '/vendor/autoload.php')) {
require_once dirname(__DIR__) . '/vendor/autoload.php';
}
#Unit test: logica pura
tests/Unit/PriceCalculatorTest.php
php
<?php
namespace EcomMimodulo\Tests\Unit;
use PHPUnit\Framework\TestCase;
use EcomMimodulo\Service\PriceCalculator;
class PriceCalculatorTest extends TestCase
{
private PriceCalculator $calculator;
protected function setUp(): void
{
$this->calculator = new PriceCalculator();
}
public function testCalculateDiscountPercent(): void
{
// 20% descuento sobre 100 EUR = 80 EUR
$result = $this->calculator->applyDiscount(100.00, 20, 'percent');
$this->assertEquals(80.00, $result);
}
public function testCalculateDiscountFixed(): void
{
// 15 EUR descuento sobre 100 EUR = 85 EUR
$result = $this->calculator->applyDiscount(100.00, 15, 'amount');
$this->assertEquals(85.00, $result);
}
public function testDiscountNeverNegative(): void
{
// Descuento mayor que precio = 0, nunca negativo
$result = $this->calculator->applyDiscount(10.00, 50, 'amount');
$this->assertEquals(0.00, $result);
}
public function testInvalidDiscountType(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->calculator->applyDiscount(100.00, 10, 'invalid');
}
/**
* @dataProvider marginProvider
*/
public function testCalculateMargin(float $price, float $cost, float $expected): void
{
$margin = $this->calculator->calculateMargin($price, $cost);
$this->assertEquals($expected, $margin);
}
public function marginProvider(): array
{
return [
'normal margin' => [100.00, 60.00, 40.0],
'zero margin' => [50.00, 50.00, 0.0],
'negative margin' => [30.00, 50.00, -66.67],
'zero cost' => [100.00, 0.00, 100.0],
];
}
}
#Integration test: con BD
tests/Integration/InstallTest.php
php
<?php
namespace EcomMimodulo\Tests\Integration;
use PHPUnit\Framework\TestCase;
use Ecom_mimodulo;
use Db;
use Configuration;
/**
* Tests de integracion — requieren PS core y BD
* Ejecutar: PS_ROOT=/var/www/prestashop phpunit --testsuite Integration
*/
class InstallTest extends TestCase
{
private static Ecom_mimodulo $module;
public static function setUpBeforeClass(): void
{
self::$module = new Ecom_mimodulo();
}
public function testModuleCanInstall(): void
{
// Desinstalar primero si ya esta instalado
if (self::$module->isInstalled(self::$module->name)) {
self::$module->uninstall();
}
$result = self::$module->install();
$this->assertTrue($result, 'El modulo debe instalarse correctamente');
}
/**
* @depends testModuleCanInstall
*/
public function testTablesCreatedAfterInstall(): void
{
$tables = Db::getInstance()->executeS('SHOW TABLES LIKE \'' . _DB_PREFIX_ . 'ecom_mimodulo%\'');
$this->assertNotEmpty($tables, 'Las tablas del modulo deben existir');
}
/**
* @depends testModuleCanInstall
*/
public function testDefaultConfigValues(): void
{
$this->assertEquals('1', Configuration::get('ECOM_MIMODULO_ENABLED'));
$this->assertNotEmpty(Configuration::get('ECOM_MIMODULO_TITLE'));
}
/**
* @depends testModuleCanInstall
*/
public function testHooksRegistered(): void
{
$this->assertTrue(
self::$module->isRegisteredInHook('displayHeader'),
'displayHeader hook debe estar registrado'
);
$this->assertTrue(
self::$module->isRegisteredInHook('actionCartSave'),
'actionCartSave hook debe estar registrado'
);
}
/**
* @depends testModuleCanInstall
*/
public function testModuleCanUninstall(): void
{
$result = self::$module->uninstall();
$this->assertTrue($result, 'El modulo debe desinstalarse correctamente');
}
/**
* @depends testModuleCanUninstall
*/
public function testTablesRemovedAfterUninstall(): void
{
$tables = Db::getInstance()->executeS('SHOW TABLES LIKE \'' . _DB_PREFIX_ . 'ecom_mimodulo%\'');
$this->assertEmpty($tables, 'Las tablas deben eliminarse tras uninstall');
}
/**
* @depends testModuleCanUninstall
*/
public function testConfigCleanedAfterUninstall(): void
{
$this->assertFalse(
Configuration::get('ECOM_MIMODULO_ENABLED'),
'Las configuraciones deben eliminarse tras uninstall'
);
}
}
#CI con GitHub Actions
.github/workflows/tests.yml
yaml
name: Module Tests
on: [push, pull_request]
jobs:
unit-tests:
runs-on: ubuntu-latest
strategy:
matrix:
php: ['7.4', '8.0', '8.1', '8.2']
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: mbstring, intl, gd, pdo_mysql
coverage: xdebug
- run: composer install --no-interaction
- name: Unit Tests
run: UNIT_ONLY=1 vendor/bin/phpunit --testsuite Unit
- name: PHP CS Fixer
run: vendor/bin/php-cs-fixer fix --dry-run --diff
integration-tests:
runs-on: ubuntu-latest
needs: unit-tests
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: prestashop_test
ports: ['3306:3306']
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.1'
- name: Install PrestaShop
run: |
git clone --depth 1 --branch 8.1.x https://github.com/PrestaShop/PrestaShop.git /tmp/ps
cd /tmp/ps && composer install --no-interaction
php install-dev/index_cli.php \
--db_server=127.0.0.1 --db_name=prestashop_test \
--db_user=root --db_password=root \
--prefix=ps_ --domain=localhost
- name: Link module
run: ln -s $GITHUB_WORKSPACE /tmp/ps/modules/ecom_mimodulo
- name: Integration Tests
run: PS_ROOT=/tmp/ps vendor/bin/phpunit --testsuite Integration
Descargar en Markdown
Pensado para pegar en ChatGPT, Claude u otra IA. Incluye solo el contenido de esta pagina.