V13Axel / neotest-pest

Neotest adapter for Pest 2.0
MIT License
14 stars 5 forks source link

PHPUnit Test Support #13

Open V13Axel opened 1 month ago

V13Axel commented 1 month ago

The goal of this PR is to add support for running PHPUnit tests. Who knows what that'll look like though.

V13Axel commented 1 month ago

So far I'm not 100% sure this possible at the moment for one reason: Identifying tests is hard. Specifically, due to what (at first, cursory glance) appears to be differences in the way Pest runs PHPUnit tests compared to standard vanilla PHPUnit.

In short, this adapter (and the neotest-phpunit adapter) parses the XML output of the --log-junit flag for PHPUnit in order to determine the outcome of the tests, and the output is not incredibly helpful.

I created a very simple test file for this:

<?php

namespace Tests\Unit;

use PHPUnit\Framework\TestCase;

class ExampleTest extends TestCase
{
    /**
     * A basic test example.
     *
     * @return void
     */
    public function testBasicTest()
    {
        $this->assertTrue(true);
    }
}

Then I placed that in tests/Unit on two different projects: One setup for PHPUnit and the other setup for Pest.


Vanilla PHPUnit

Running phpunit --log-junit=storage/app/examplelog.xml --filter=ExampleTest, here's the contents of storage/app/examplelog.xml

<?xml version="1.0" encoding="UTF-8"?>
<testsuites>
  <testsuite name="/var/task/phpunit.xml" tests="1" assertions="1" errors="0" failures="0" skipped="0" time="0.001678">
    <testsuite name="Unit" tests="1" assertions="1" errors="0" failures="0" skipped="0" time="0.001678">
      <testsuite name="Tests\Unit\ExampleTest" file="/var/task/tests/Unit/ExampleTest.php" tests="1" assertions="1" errors="0" failures="0" skipped="0" time="0.001678">
        <testcase name="testBasicTest" file="/var/task/tests/Unit/ExampleTest.php" line="14" class="Tests\Unit\ExampleTest" classname="Tests.Unit.ExampleTest" assertions="1" time="0.001678"/>
      </testsuite>
    </testsuite>
  </testsuite>
</testsuites>

Looks like we have everything we need in order to determine exactly where that test is, on disk: File name, test case method name, line number... It's all there.


However, if we compare that against the same ExampleTest file, but run through Pest instead:

<?xml version="1.0" encoding="UTF-8"?>
<testsuites>
  <testsuite name="Tests\Unit\ExampleTest" file="Example (Tests\Unit\Example)" tests="1" assertions="1" errors="0" failures="0" skipped="0" time="0.001652">
    <testcase name="Basic test" file="Example (Tests\Unit\Example)::Basic test" class="Tests\Unit\ExampleTest" classname="Tests.Unit.ExampleTest" assertions="1" time="0.001652"/>
  </testsuite>
</testsuites>

The "file" attribute is no longer the file path, but instead ClassName (ClassPath). There is a testcase name, though! So not all hope is lost, I think.


However, here's what neotest provides as the 'position' data to evaluate when discovering tests, from which the adapter has to create a unique identifier:

{
    name = "testBasicTest",
    path = "/home/axel/Git/neotest-pest/tests/Unit/ExampleTest.php",
    range = { 13, 4, 16, 5 },
    type = "test"
}

I have to use that data to create an ID. Then, I have to use the data from the XML to create that same ID so I can match up test <-> result.

It almost feels like all the data I need is there, but needs reformatting. I'm not 100% confident, but I think the adapter can make some assumptions and wind up with something workable.