typo3-testing
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTYPO3 Testing Skill
TYPO3 测试技能
Comprehensive testing infrastructure for TYPO3 extensions: unit, functional, E2E, architecture, and mutation testing.
TYPO3 API First: Always use TYPO3's built-in APIs, core features, and established conventions before creating custom implementations. Do not reinvent what TYPO3 already provides. Always verify that the APIs and methods you use exist and are not deprecated in your target TYPO3 version (v13 or v14) by checking the official TYPO3 documentation.
面向TYPO3扩展的全面测试基础设施:包含单元测试、功能测试、端到端(E2E)测试、架构测试以及变异测试。
TYPO3 API优先原则: 在创建自定义实现之前,请始终使用TYPO3的内置API、核心功能和既定规范。不要重复造轮子,TYPO3已提供的功能直接使用即可。请务必通过官方TYPO3文档验证你所使用的API和方法在目标TYPO3版本(v13或v14)中存在且未被弃用。
Test Type Selection
测试类型选择
| Type | Use When | Speed | Framework |
|---|---|---|---|
| Unit | Pure logic, validators, utilities | Fast (ms) | PHPUnit |
| Functional | DB interactions, repositories | Medium (s) | PHPUnit + TYPO3 |
| Architecture | Layer constraints, dependencies | Fast (ms) | PHPat |
| E2E | User workflows, browser | Slow (s-min) | Playwright |
| Mutation | Test quality verification | CI only | Infection |
| 类型 | 适用场景 | 执行速度 | 使用框架 |
|---|---|---|---|
| 单元测试 | 纯逻辑、验证器、工具类 | 快(毫秒级) | PHPUnit |
| 功能测试 | 数据库交互、仓库层 | 中等(秒级) | PHPUnit + TYPO3 |
| 架构测试 | 分层约束、依赖关系 | 快(毫秒级) | PHPat |
| 端到端测试(E2E) | 用户工作流、浏览器交互 | 慢(秒-分钟级) | Playwright |
| 变异测试 | 测试质量验证 | 仅CI环境使用 | Infection |
Test Infrastructure Setup
测试基础设施搭建
Directory Structure
目录结构
Tests/
├── Functional/
│ ├── Controller/
│ ├── Repository/
│ └── Fixtures/
├── Unit/
│ ├── Service/
│ └── Validator/
├── Architecture/
│ └── ArchitectureTest.php
└── E2E/
└── playwright/Tests/
├── Functional/
│ ├── Controller/
│ ├── Repository/
│ └── Fixtures/
├── Unit/
│ ├── Service/
│ └── Validator/
├── Architecture/
│ └── ArchitectureTest.php
└── E2E/
└── playwright/PHPUnit Configuration
PHPUnit 配置
xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/typo3/testing-framework/Resources/Core/Build/UnitTestsBootstrap.php"
colors="true"
cacheResult="false">
<testsuites>
<testsuite name="Unit">
<directory>Tests/Unit</directory>
</testsuite>
<testsuite name="Functional">
<directory>Tests/Functional</directory>
</testsuite>
<testsuite name="Architecture">
<directory>Tests/Architecture</directory>
</testsuite>
</testsuites>
<coverage>
<report>
<clover outputFile="var/log/coverage.xml"/>
<html outputDirectory="var/log/coverage"/>
</report>
</coverage>
<source>
<include>
<directory>Classes</directory>
</include>
<exclude>
<directory>Classes/Domain/Model</directory>
</exclude>
</source>
</phpunit>xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/typo3/testing-framework/Resources/Core/Build/UnitTestsBootstrap.php"
colors="true"
cacheResult="false">
<testsuites>
<testsuite name="Unit">
<directory>Tests/Unit</directory>
</testsuite>
<testsuite name="Functional">
<directory>Tests/Functional</directory>
</testsuite>
<testsuite name="Architecture">
<directory>Tests/Architecture</directory>
</testsuite>
</testsuites>
<coverage>
<report>
<clover outputFile="var/log/coverage.xml"/>
<html outputDirectory="var/log/coverage"/>
</report>
</coverage>
<source>
<include>
<directory>Classes</directory>
</include>
<exclude>
<directory>Classes/Domain/Model</directory>
</exclude>
</source>
</phpunit>Functional Test Configuration
功能测试配置
xml
<!-- FunctionalTests.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/typo3/testing-framework/Resources/Core/Build/FunctionalTestsBootstrap.php"
colors="true">
<testsuites>
<testsuite name="Functional">
<directory>Tests/Functional</directory>
</testsuite>
</testsuites>
</phpunit>xml
<!-- FunctionalTests.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
bootstrap="vendor/typo3/testing-framework/Resources/Core/Build/FunctionalTestsBootstrap.php"
colors="true">
<testsuites>
<testsuite name="Functional">
<directory>Tests/Functional</directory>
</testsuite>
</testsuites>
</phpunit>Unit Testing
单元测试
Basic Unit Test
基础单元测试示例
php
<?php
declare(strict_types=1);
namespace Vendor\MyExtension\Tests\Unit\Service;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Vendor\MyExtension\Service\PriceCalculator;
final class PriceCalculatorTest extends TestCase
{
private PriceCalculator $subject;
protected function setUp(): void
{
parent::setUp();
$this->subject = new PriceCalculator();
}
#[Test]
public function calculateNetPriceReturnsCorrectValue(): void
{
$grossPrice = 119.00;
$taxRate = 19.0;
$netPrice = $this->subject->calculateNetPrice($grossPrice, $taxRate);
self::assertEqualsWithDelta(100.00, $netPrice, 0.01);
}
#[Test]
public function calculateNetPriceThrowsExceptionForNegativePrice(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionCode(1234567890);
$this->subject->calculateNetPrice(-10.00, 19.0);
}
}php
<?php
declare(strict_types=1);
namespace Vendor\MyExtension\Tests\Unit\Service;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use Vendor\MyExtension\Service\PriceCalculator;
final class PriceCalculatorTest extends TestCase
{
private PriceCalculator $subject;
protected function setUp(): void
{
parent::setUp();
$this->subject = new PriceCalculator();
}
#[Test]
public function calculateNetPriceReturnsCorrectValue(): void
{
$grossPrice = 119.00;
$taxRate = 19.0;
$netPrice = $this->subject->calculateNetPrice($grossPrice, $taxRate);
self::assertEqualsWithDelta(100.00, $netPrice, 0.01);
}
#[Test]
public function calculateNetPriceThrowsExceptionForNegativePrice(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionCode(1234567890);
$this->subject->calculateNetPrice(-10.00, 19.0);
}
}Mocking Dependencies
依赖项模拟
php
<?php
declare(strict_types=1);
namespace Vendor\MyExtension\Tests\Unit\Service;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Vendor\MyExtension\Service\ItemService;
use Vendor\MyExtension\Domain\Repository\ItemRepository;
final class ItemServiceTest extends TestCase
{
private ItemRepository&MockObject $itemRepositoryMock;
private LoggerInterface&MockObject $loggerMock;
private ItemService $subject;
protected function setUp(): void
{
parent::setUp();
$this->itemRepositoryMock = $this->createMock(ItemRepository::class);
$this->loggerMock = $this->createMock(LoggerInterface::class);
$this->subject = new ItemService(
$this->itemRepositoryMock,
$this->loggerMock,
);
}
#[Test]
public function findActiveItemsReturnsFilteredItems(): void
{
$items = [/* mock items */];
$this->itemRepositoryMock
->expects(self::once())
->method('findByActive')
->with(true)
->willReturn($items);
$result = $this->subject->findActiveItems();
self::assertSame($items, $result);
}
}php
<?php
declare(strict_types=1);
namespace Vendor\MyExtension\Tests\Unit\Service;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Vendor\MyExtension\Service\ItemService;
use Vendor\MyExtension\Domain\Repository\ItemRepository;
final class ItemServiceTest extends TestCase
{
private ItemRepository&MockObject $itemRepositoryMock;
private LoggerInterface&MockObject $loggerMock;
private ItemService $subject;
protected function setUp(): void
{
parent::setUp();
$this->itemRepositoryMock = $this->createMock(ItemRepository::class);
$this->loggerMock = $this->createMock(LoggerInterface::class);
$this->subject = new ItemService(
$this->itemRepositoryMock,
$this->loggerMock,
);
}
#[Test]
public function findActiveItemsReturnsFilteredItems(): void
{
$items = [/* mock items */];
$this->itemRepositoryMock
->expects(self::once())
->method('findByActive')
->with(true)
->willReturn($items);
$result = $this->subject->findActiveItems();
self::assertSame($items, $result);
}
}Functional Testing
功能测试
Repository Test
仓库层测试示例
php
<?php
declare(strict_types=1);
namespace Vendor\MyExtension\Tests\Functional\Repository;
use PHPUnit\Framework\Attributes\Test;
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
use Vendor\MyExtension\Domain\Repository\ItemRepository;
final class ItemRepositoryTest extends FunctionalTestCase
{
protected array $testExtensionsToLoad = [
'typo3conf/ext/my_extension',
];
private ItemRepository $subject;
protected function setUp(): void
{
parent::setUp();
$this->importCSVDataSet(__DIR__ . '/Fixtures/Items.csv');
$this->subject = $this->get(ItemRepository::class);
}
#[Test]
public function findByUidReturnsCorrectItem(): void
{
$item = $this->subject->findByUid(1);
self::assertNotNull($item);
self::assertSame('Test Item', $item->getTitle());
}
#[Test]
public function findAllReturnsAllItems(): void
{
$items = $this->subject->findAll();
self::assertCount(3, $items);
}
}php
<?php
declare(strict_types=1);
namespace Vendor\MyExtension\Tests\Functional\Repository;
use PHPUnit\Framework\Attributes\Test;
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
use Vendor\MyExtension\Domain\Repository\ItemRepository;
final class ItemRepositoryTest extends FunctionalTestCase
{
protected array $testExtensionsToLoad = [
'typo3conf/ext/my_extension',
];
private ItemRepository $subject;
protected function setUp(): void
{
parent::setUp();
$this->importCSVDataSet(__DIR__ . '/Fixtures/Items.csv');
$this->subject = $this->get(ItemRepository::class);
}
#[Test]
public function findByUidReturnsCorrectItem(): void
{
$item = $this->subject->findByUid(1);
self::assertNotNull($item);
self::assertSame('Test Item', $item->getTitle());
}
#[Test]
public function findAllReturnsAllItems(): void
{
$items = $this->subject->findAll();
self::assertCount(3, $items);
}
}CSV Fixture Format
CSV 测试数据格式
csv
undefinedcsv
undefinedTests/Functional/Repository/Fixtures/Items.csv
Tests/Functional/Repository/Fixtures/Items.csv
"tx_myext_items"
,"uid","pid","title","active","deleted"
,1,1,"Test Item",1,0
,2,1,"Another Item",1,0
,3,1,"Inactive Item",0,0
undefined"tx_myext_items"
,"uid","pid","title","active","deleted"
,1,1,"Test Item",1,0
,2,1,"Another Item",1,0
,3,1,"Inactive Item",0,0
undefinedController Functional Test
控制器功能测试示例
php
<?php
declare(strict_types=1);
namespace Vendor\MyExtension\Tests\Functional\Controller;
use PHPUnit\Framework\Attributes\Test;
use TYPO3\CMS\Core\Http\ServerRequest;
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
use Vendor\MyExtension\Controller\ItemController;
final class ItemControllerTest extends FunctionalTestCase
{
protected array $testExtensionsToLoad = [
'typo3conf/ext/my_extension',
];
#[Test]
public function listActionReturnsHtmlResponse(): void
{
$this->importCSVDataSet(__DIR__ . '/Fixtures/Pages.csv');
$this->importCSVDataSet(__DIR__ . '/Fixtures/Items.csv');
$request = new ServerRequest('https://example.com/items', 'GET');
$controller = $this->get(ItemController::class);
$response = $controller->listAction();
self::assertSame(200, $response->getStatusCode());
self::assertStringContainsString('text/html', $response->getHeaderLine('Content-Type'));
}
}php
<?php
declare(strict_types=1);
namespace Vendor\MyExtension\Tests\Functional\Controller;
use PHPUnit\Framework\Attributes\Test;
use TYPO3\CMS\Core\Http\ServerRequest;
use TYPO3\TestingFramework\Core\Functional\FunctionalTestCase;
use Vendor\MyExtension\Controller\ItemController;
final class ItemControllerTest extends FunctionalTestCase
{
protected array $testExtensionsToLoad = [
'typo3conf/ext/my_extension',
];
#[Test]
public function listActionReturnsHtmlResponse(): void
{
$this->importCSVDataSet(__DIR__ . '/Fixtures/Pages.csv');
$this->importCSVDataSet(__DIR__ . '/Fixtures/Items.csv');
$request = new ServerRequest('https://example.com/items', 'GET');
$controller = $this->get(ItemController::class);
$response = $controller->listAction();
self::assertSame(200, $response->getStatusCode());
self::assertStringContainsString('text/html', $response->getHeaderLine('Content-Type'));
}
}Architecture Testing with PHPat
基于PHPat的架构测试
Installation
安装方法
bash
composer require --dev phpat/phpatbash
composer require --dev phpat/phpatArchitecture Test
架构测试示例
php
<?php
declare(strict_types=1);
namespace Vendor\MyExtension\Tests\Architecture;
use PHPat\Selector\Selector;
use PHPat\Test\Builder\Rule;
use PHPat\Test\PHPat;
final class ArchitectureTest
{
public function testDomainModelsShouldNotDependOnInfrastructure(): Rule
{
return PHPat::rule()
->classes(Selector::inNamespace('Vendor\MyExtension\Domain\Model'))
->shouldNotDependOn()
->classes(
Selector::inNamespace('Vendor\MyExtension\Controller'),
Selector::inNamespace('Vendor\MyExtension\Infrastructure'),
);
}
public function testServicesShouldNotDependOnControllers(): Rule
{
return PHPat::rule()
->classes(Selector::inNamespace('Vendor\MyExtension\Service'))
->shouldNotDependOn()
->classes(Selector::inNamespace('Vendor\MyExtension\Controller'));
}
public function testRepositoriesShouldImplementInterface(): Rule
{
return PHPat::rule()
->classes(Selector::classname('/.*Repository$/', true))
->excluding(Selector::classname('/.*Interface$/', true))
->shouldImplement()
->classes(Selector::classname('/.*RepositoryInterface$/', true));
}
public function testOnlyServicesCanAccessRepositories(): Rule
{
return PHPat::rule()
->classes(Selector::inNamespace('Vendor\MyExtension\Domain\Repository'))
->canOnlyBeAccessedBy()
->classes(
Selector::inNamespace('Vendor\MyExtension\Service'),
Selector::inNamespace('Vendor\MyExtension\Tests'),
);
}
}php
<?php
declare(strict_types=1);
namespace Vendor\MyExtension\Tests\Architecture;
use PHPat\Selector\Selector;
use PHPat\Test\Builder\Rule;
use PHPat\Test\PHPat;
final class ArchitectureTest
{
public function testDomainModelsShouldNotDependOnInfrastructure(): Rule
{
return PHPat::rule()
->classes(Selector::inNamespace('Vendor\MyExtension\Domain\Model'))
->shouldNotDependOn()
->classes(
Selector::inNamespace('Vendor\MyExtension\Controller'),
Selector::inNamespace('Vendor\MyExtension\Infrastructure'),
);
}
public function testServicesShouldNotDependOnControllers(): Rule
{
return PHPat::rule()
->classes(Selector::inNamespace('Vendor\MyExtension\Service'))
->shouldNotDependOn()
->classes(Selector::inNamespace('Vendor\MyExtension\Controller'));
}
public function testRepositoriesShouldImplementInterface(): Rule
{
return PHPat::rule()
->classes(Selector::classname('/.*Repository$/', true))
->excluding(Selector::classname('/.*Interface$/', true))
->shouldImplement()
->classes(Selector::classname('/.*RepositoryInterface$/', true));
}
public function testOnlyServicesCanAccessRepositories(): Rule
{
return PHPat::rule()
->classes(Selector::inNamespace('Vendor\MyExtension\Domain\Repository'))
->canOnlyBeAccessedBy()
->classes(
Selector::inNamespace('Vendor\MyExtension\Service'),
Selector::inNamespace('Vendor\MyExtension\Tests'),
);
}
}PHPat Configuration
PHPat 配置
neon
undefinedneon
undefinedphpstan.neon
phpstan.neon
includes:
- vendor/phpat/phpat/extension.neon
parameters:
level: 9
paths:
- Classes
- Tests
undefinedincludes:
- vendor/phpat/phpat/extension.neon
parameters:
level: 9
paths:
- Classes
- Tests
undefinedE2E Testing with Playwright
基于Playwright的端到端(E2E)测试
Setup
环境搭建
bash
undefinedbash
undefinedInstall Playwright
Install Playwright
npm init playwright@latest
npm init playwright@latest
Configure for TYPO3
Configure for TYPO3
mkdir -p Tests/E2E/playwright
undefinedmkdir -p Tests/E2E/playwright
undefinedPlaywright Configuration
Playwright 配置
typescript
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './Tests/E2E',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: process.env.BASE_URL || 'https://my-extension.ddev.site',
trace: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
});typescript
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './Tests/E2E',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: process.env.BASE_URL || 'https://my-extension.ddev.site',
trace: 'on-first-retry',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
});E2E Test Example
E2E 测试示例
typescript
// Tests/E2E/item-list.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Item List', () => {
test('displays items correctly', async ({ page }) => {
await page.goto('/items');
await expect(page.locator('h1')).toContainText('Items');
await expect(page.locator('.item-card')).toHaveCount(3);
});
test('filters items by category', async ({ page }) => {
await page.goto('/items');
await page.selectOption('[data-testid="category-filter"]', 'electronics');
await expect(page.locator('.item-card')).toHaveCount(1);
});
test('creates new item', async ({ page }) => {
await page.goto('/items/new');
await page.fill('[name="title"]', 'New Test Item');
await page.fill('[name="description"]', 'Test description');
await page.click('[type="submit"]');
await expect(page).toHaveURL(/\/items\/\d+/);
await expect(page.locator('h1')).toContainText('New Test Item');
});
});typescript
// Tests/E2E/item-list.spec.ts
import { test, expect } from '@playwright/test';
test.describe('Item List', () => {
test('displays items correctly', async ({ page }) => {
await page.goto('/items');
await expect(page.locator('h1')).toContainText('Items');
await expect(page.locator('.item-card')).toHaveCount(3);
});
test('filters items by category', async ({ page }) => {
await page.goto('/items');
await page.selectOption('[data-testid="category-filter"]', 'electronics');
await expect(page.locator('.item-card')).toHaveCount(1);
});
test('creates new item', async ({ page }) => {
await page.goto('/items/new');
await page.fill('[name="title"]', 'New Test Item');
await page.fill('[name="description"]', 'Test description');
await page.click('[type="submit"]');
await expect(page).toHaveURL(/\/items\/\d+/);
await expect(page.locator('h1')).toContainText('New Test Item');
});
});Mutation Testing with Infection
基于Infection的变异测试
Installation
安装方法
bash
composer require --dev infection/infectionbash
composer require --dev infection/infectionConfiguration
配置文件
json
// infection.json5
{
"$schema": "vendor/infection/infection/resources/schema.json",
"source": {
"directories": ["Classes"],
"excludes": ["Domain/Model"]
},
"logs": {
"text": "var/log/infection.log",
"html": "var/log/infection.html"
},
"mutators": {
"@default": true
},
"minMsi": 70,
"minCoveredMsi": 80
}json
// infection.json5
{
"$schema": "vendor/infection/infection/resources/schema.json",
"source": {
"directories": ["Classes"],
"excludes": ["Domain/Model"]
},
"logs": {
"text": "var/log/infection.log",
"html": "var/log/infection.html"
},
"mutators": {
"@default": true
},
"minMsi": 70,
"minCoveredMsi": 80
}Run Mutation Tests
运行变异测试
bash
vendor/bin/infection --threads=4bash
vendor/bin/infection --threads=4Test Commands
测试命令汇总
bash
undefinedbash
undefinedUnit tests
Unit tests
vendor/bin/phpunit -c Tests/UnitTests.xml
vendor/bin/phpunit -c Tests/UnitTests.xml
Functional tests
Functional tests
vendor/bin/phpunit -c Tests/FunctionalTests.xml
vendor/bin/phpunit -c Tests/FunctionalTests.xml
Architecture tests
Architecture tests
vendor/bin/phpstan analyse
vendor/bin/phpstan analyse
All tests with coverage
All tests with coverage
vendor/bin/phpunit --coverage-html var/log/coverage
vendor/bin/phpunit --coverage-html var/log/coverage
E2E tests
E2E tests
npx playwright test
npx playwright test
Mutation tests
Mutation tests
vendor/bin/infection
undefinedvendor/bin/infection
undefinedCI/CD Configuration
CI/CD 配置示例
yaml
undefinedyaml
undefined.github/workflows/tests.yml
.github/workflows/tests.yml
name: Tests
on: [push, pull_request]
jobs:
unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
coverage: xdebug
- run: composer install
- run: vendor/bin/phpunit -c Tests/UnitTests.xml --coverage-clover coverage.xml
functional:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: test
ports:
- 3306:3306
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
- run: composer install
- run: vendor/bin/phpunit -c Tests/FunctionalTests.xml
architecture:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
- run: composer install
- run: vendor/bin/phpstan analyse
undefinedname: Tests
on: [push, pull_request]
jobs:
unit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
coverage: xdebug
- run: composer install
- run: vendor/bin/phpunit -c Tests/UnitTests.xml --coverage-clover coverage.xml
functional:
runs-on: ubuntu-latest
services:
mysql:
image: mysql:8.0
env:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: test
ports:
- 3306:3306
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
- run: composer install
- run: vendor/bin/phpunit -c Tests/FunctionalTests.xml
architecture:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.3'
- run: composer install
- run: vendor/bin/phpstan analyse
undefinedScoring Requirements
测试评分要求
| Criterion | Requirement |
|---|---|
| Unit tests | Required, 70%+ coverage |
| Functional tests | Required for DB operations |
| Architecture tests | PHPat required for full conformance |
| PHPStan | Level 9+ (level 10 recommended) |
| E2E tests | Optional, bonus points |
| Mutation | 70%+ MSI for bonus points |
| 评估项 | 要求 |
|---|---|
| 单元测试 | 必须覆盖,代码覆盖率≥70% |
| 功能测试 | 涉及数据库操作的功能必须覆盖 |
| 架构测试 | 必须使用PHPat以确保完全符合规范 |
| PHPStan | 规则等级≥9(推荐等级10) |
| E2E测试 | 可选,完成可获得额外加分 |
| 变异测试 | MSI≥70%可获得额外加分 |
Credits & Attribution
致谢与贡献
Thanks to Netresearch DTT GmbH for their contributions to the TYPO3 community.
感谢Netresearch DTT GmbH为TYPO3社区所做的贡献。