Skip to content

Commit 3f63ce6

Browse files
committed
Merge branch 'refs/heads/1.x' into feat/add-assert-equal-not-equal
2 parents eb3da87 + 73ac506 commit 3f63ce6

File tree

8 files changed

+161
-25
lines changed

8 files changed

+161
-25
lines changed

README.md

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<p align="center">
2-
<a href="#installation"><img alt="TESTO"
2+
<a href="#get-started"><img alt="TESTO"
33
src="https://github.com/php-testo/.github/blob/1.x/resources/logo-full.svg?raw=true"
44
style="width: 2in; display: block"
55
/></a>
@@ -16,7 +16,13 @@
1616

1717
<br />
1818

19-
## Installation
19+
Testo is an extensible testing framework built on a lightweight core with a middleware system.
20+
It gives you full control over your testing environment while keeping the familiar PHP syntax you already know.
21+
22+
23+
## Get Started
24+
25+
### Installation
2026

2127
```bash
2228
composer require testo/testo
@@ -27,6 +33,93 @@ composer require testo/testo
2733
[![License](https://img.shields.io/packagist/l/testo/testo.svg?style=flat-square)](LICENSE.md)
2834
[![Total Destroys](https://img.shields.io/packagist/dt/testo/testo.svg?style=flat-square)](https://packagist.org/packages/testo/testo/stats)
2935

36+
### Configuration
37+
38+
By default, if no configuration file is provided, Testo will run tests from the `tests` folder.
39+
40+
To customize the configuration, create a `testo.php` file in the root of your project:
41+
42+
```php
43+
<?php
44+
45+
declare(strict_types=1);
46+
47+
use Testo\Config\ApplicationConfig;
48+
use Testo\Config\SuiteConfig;
49+
use Testo\Config\FinderConfig;
50+
51+
return new ApplicationConfig(
52+
suites: [
53+
new SuiteConfig(
54+
name: 'Unit',
55+
location: new FinderConfig(
56+
include: ['tests/Unit'],
57+
),
58+
),
59+
],
60+
);
61+
```
62+
63+
### Running Tests
64+
65+
To run your tests, execute:
66+
67+
```bash
68+
vendor/bin/testo
69+
```
70+
71+
### Writing Your First Test
72+
73+
Create a test class in the configured test directory (e.g., `tests/CalculatorTest.php`):
74+
75+
```php
76+
<?php
77+
78+
declare(strict_types=1);
79+
80+
namespace Tests;
81+
82+
use Testo\Assert;
83+
use Testo\Attribute\Test;
84+
use Testo\Attribute\RetryPolicy;
85+
use Testo\Attribute\ExpectException;
86+
87+
final class CalculatorTest
88+
{
89+
#[Test]
90+
public function dividesNumbers(): void
91+
{
92+
$result = 10 / 2;
93+
94+
Assert::same(5.0, $result);
95+
Assert::notSame(5, $result); // Types matter!
96+
}
97+
98+
#[Test]
99+
#[RetryPolicy(maxAttempts: 3)]
100+
public function flakyApiCall(): void
101+
{
102+
// Retries up to 3 times if test fails
103+
$response = $this->makeExternalApiCall();
104+
105+
Assert::same(200, $response->status);
106+
}
107+
108+
#[Test]
109+
#[ExpectException(\RuntimeException::class)]
110+
public function throwsException(): void
111+
{
112+
throw new \RuntimeException('Expected error');
113+
}
114+
}
115+
```
116+
117+
What to note:
118+
- Use the `#[Test]` attribute to mark test methods
119+
- Test classes don't need to extend any base class
120+
- Use `Assert` class for assertions (`same`, `true`, `false`, `null`, `contains`, `instanceOf`, etc.)
121+
- Testo provides multiple attributes to extend testing capabilities (retry policies, exception handling, and more)
122+
30123
## IDE Support
31124

32125
Testo comes with the [IDEA plugin `Testo`](https://plugins.jetbrains.com/plugin/28842-testo?noRedirect=true).

src/Application.php

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Testo\Common\Container;
88
use Testo\Common\Filter;
99
use Testo\Common\Internal\ObjectContainer;
10+
use Testo\Common\Path;
1011
use Testo\Config\ApplicationConfig;
1112
use Testo\Config\Internal\ConfigInflector;
1213
use Testo\Config\ServicesConfig;
@@ -39,13 +40,13 @@ public static function createFromConfig(
3940
/**
4041
* Create the application instance from ENV, CLI arguments, and config file.
4142
*
42-
* @param non-empty-string|null $xml Path to XML file or raw XML content
43+
* @param Path|null $configFile Path to config file
4344
* @param array<string, mixed> $inputOptions Command-line options
4445
* @param array<string, mixed> $inputArguments Command-line arguments
4546
* @param array<string, string> $environment Environment variables
4647
*/
4748
public static function createFromInput(
48-
?string $xml = null,
49+
?Path $configFile = null,
4950
array $inputOptions = [],
5051
array $inputArguments = [],
5152
array $environment = [],
@@ -57,8 +58,20 @@ public static function createFromInput(
5758
'inputOptions' => $inputOptions,
5859
];
5960

60-
# Read XML config file
61-
// $xml === null or $args['xml'] = $this->readConfig($xml);
61+
# Bind reading provided config file
62+
$configFile === null or $container
63+
->bind(ApplicationConfig::class, function () use ($configFile): ApplicationConfig {
64+
$cfg = include $configFile;
65+
$cfg instanceof ApplicationConfig or throw new \InvalidArgumentException(
66+
\sprintf(
67+
'Configuration file %s must return an instance of %s, %s returned.',
68+
$configFile,
69+
ApplicationConfig::class,
70+
\is_object($cfg) ? \get_class($cfg) : \gettype($cfg),
71+
),
72+
);
73+
return $cfg;
74+
});
6275

6376
# Register Config inflector
6477
$container->addInflector($container->make(ConfigInflector::class, $args));

src/Assert/Interceptor/ObjectTrackerInterceptor.php

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,7 @@
44

55
namespace Testo\Assert\Interceptor;
66

7-
use Testo\Assert\State;
87
use Testo\Assert\State\AssertException;
9-
use Testo\Assert\State\Record;
10-
use Testo\Assert\State\Success;
118
use Testo\Assert\StaticState;
129
use Testo\Interceptor\TestRunInterceptor;
1310
use Testo\Test\Dto\Status;

src/Common/Command/Base.php

Lines changed: 32 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,7 @@
1212
use Symfony\Component\Console\Style\SymfonyStyle;
1313
use Testo\Application;
1414
use Testo\Common\Container;
15-
use Testo\Config\ApplicationConfig;
16-
use Testo\Config\FinderConfig;
17-
use Testo\Config\SuiteConfig;
15+
use Testo\Common\Path;
1816
use Yiisoft\Injector\Injector;
1917

2018
/**
@@ -72,17 +70,12 @@ protected function execute(
7270
InputInterface $input,
7371
OutputInterface $output,
7472
): int {
75-
$cfg = new ApplicationConfig(
76-
src: new FinderConfig(['src']),
77-
suites: [
78-
new SuiteConfig(
79-
name: 'default',
80-
location: new FinderConfig(['tests/Testo']),
81-
),
82-
],
73+
$this->application = Application::createFromInput(
74+
configFile: $this->getConfigFile($input),
75+
inputOptions: $input->getOptions(),
76+
inputArguments: $input->getArguments(),
77+
environment: \getenv(),
8378
);
84-
85-
$this->application = Application::createFromConfig($cfg);
8679
$this->container = $this->application->getContainer();
8780

8881
$this->container->set($input, InputInterface::class);
@@ -91,4 +84,30 @@ protected function execute(
9184

9285
return $this->container->get(Injector::class)->invoke($this) ?? Command::SUCCESS;
9386
}
87+
88+
/**
89+
* Resolves configuration file path from input or default location.
90+
*
91+
* @param InputInterface $input Command input
92+
*
93+
* @return Path|null Path to the configuration file
94+
*/
95+
protected function getConfigFile(InputInterface $input): ?Path
96+
{
97+
/** @var string|null $config */
98+
$config = $input->getOption('config');
99+
$isConfigured = $config !== null;
100+
// $config ??= './testo.xml';
101+
$config ??= './testo.php';
102+
103+
if (\is_file($config)) {
104+
return Path::create($config);
105+
}
106+
107+
$isConfigured and throw new \InvalidArgumentException(
108+
'Configuration file not found: ' . $config,
109+
);
110+
111+
return null;
112+
}
94113
}

src/Common/Command/Run.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66

77
use Symfony\Component\Console\Attribute\AsCommand;
88
use Symfony\Component\Console\Command\Command;
9-
use Symfony\Component\Console\Input\InputArgument;
109
use Symfony\Component\Console\Input\InputInterface;
1110
use Symfony\Component\Console\Input\InputOption;
1211
use Symfony\Component\Console\Output\OutputInterface;

src/Render/Terminal/Formatter.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,6 @@ public static function assertionHistoryHeader(OutputFormat $format): string
265265
* Formats a single assertion line.
266266
*
267267
* @param \Testo\Assert\State\Record $assertion
268-
* @param OutputFormat $format
269268
* @return non-empty-string
270269
*/
271270
public static function assertionLine(object $assertion, OutputFormat $format): string

src/Render/TerminalInterceptor.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
use Testo\Interceptor\TestRunInterceptor;
99
use Testo\Interceptor\TestSuiteRunInterceptor;
1010
use Testo\Render\Terminal\ColorMode;
11-
use Testo\Render\Terminal\OutputFormat;
1211
use Testo\Render\Terminal\Style;
1312
use Testo\Render\Terminal\TerminalLogger;
1413
use Testo\Test\Dto\CaseInfo;

testo.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
use Testo\Config\ApplicationConfig;
6+
use Testo\Config\SuiteConfig;
7+
8+
return new ApplicationConfig(
9+
suites: [
10+
new SuiteConfig(
11+
name: 'default',
12+
location: new \Testo\Config\FinderConfig(
13+
include: ['tests/Testo'],
14+
),
15+
),
16+
],
17+
);

0 commit comments

Comments
 (0)