PHP unit testing on .NET, naturally

Testing is an essential part of software development. On .NET, we are used to the certain comfort and simplicity of managing our tests, running them, debugging them, collecting reports, profiling, and keeping track of the code coverage, within a single click. Does this work with PHP on .NET as well?

State of the art

All the platforms have their own way of testing your product. In the PHP world, the most standardized way is to use the PHPUnit testing framework. It is a well maintained, straightforward concept. Test files with test methods are usually in a dedicated folder, annotated with a configuration file. The developer installs the composer package “phpunit/phpunit” containing the whole framework and some command-line utilities to run your tests. So far so good. Debugging, profiling, code coverage, and producing easily readable reports, however, are a whole other story.

.NET, on the other hand, provides a unified interface for all these tasks. The actual testing framework is also defined as a package dependency; however, its responsibility is only to implement a common interface understood by all the .NET tools – the testing framework is an extension to the so-called Visual Studio Test Platform, or “VSTest”.

The goal

Since we are already providing PHP running on .NET, it is in principle necessary to also integrate the corresponding tests. Also, the point of running something on .NET is to take advantage of its features, including the testing infrastructure. Lets look at what we get by providing VSTest for PHP:

The Visual Studio Test Explorer allows to list tests, organize them by traits (aka groups and suits), run the tests, debug and profile them and check the code coverage, all in the Visual Studio UI. This is the same approach and user experience as with other .NET projects, which you can in fact have side by side, in the same test explorer window.

test explorer

Azure DevOps. It’s common practice to use an integration platform like Azure DevOps, AppVeyor, or similar, which already understands the VSTest concept and provides you with a neat summary, overall reports across all your commits, and notifications whenever a test fails. It keeps track of the health of your project and automatically creates issues from any failed tests.

Command-line interface; on .NET we have a common command that takes care of running the tests. It provides a unified output, has common parameters for filtering tests, and much more. It works everywhere, for every .NET language, using any testing framework.

> dotnet test
Test run for lib.dll(.NETCoreApp,Version=v3.1)
Microsoft (R) Test Execution Command Line Tool Version 16.6.0
Copyright (c) Microsoft Corporation.  All rights reserved.

Starting test execution, please wait...

A total of 1 test files matched the specified pattern.

Test Run Successful.
Total tests: 3
     Passed: 3
 Total time: 4.2106 Seconds

Profiling, Debugging and Code Coverage – a particularly neat feature, especially because it is handled nicely by the .NET debugger itself. This means no setups, no additional installations, no additional packages, nor extensions.

The convenience of this solution is superb. If you don’t know Visual Studio diagnostic tools, take a look at what you’ll get:

The tool will highlight hot paths and statements in your code causing the most trouble, all of which now works for PHP, as well.

Sample Project

So how do we get PHP inside VSTest? In short, create an executable project file referencing our Test Adapter package called “PHPUnit.TestAdapter“.

<Project Sdk="Peachpie.NET.Sdk/1.0.0-preview3">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <Compile Include="**/*.php" />
    <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.6.1" />
    <PackageReference Include="PHPUnit.TestAdapter" Version="9.2.6-preview3" />
  </ItemGroup>
</Project>

Save the file above as anything.msbuildproj, within the project folder. Below is the file structure:

  • anything.msbuildproj
  • phpunit.xml
  • src
    • email.php
  • tests
    • emailtest.php

Notice that the project file states the target framework to be an executable application. Then we add a dependency on our test adapter PHPUnit.TestAdapter and Microsoft.NET.Test.Sdk providing VSTest itself.

In this sample, we also have pre-release versions of the packages – “preview3” – which we will update in the future.

Also, notice the version of PHPUnit.TestAdapter – 9.2.6 – which corresponds to the version of PHPUnit being used, as described in greater detail in the following section.

PHPUnit Test Adapter

The package PHPUnit.TestAdapter provides an extension to VSTest, which is perfectly understood by Visual Studio, the command-line tool dotnet test, and others. But in order to discover and execute the tests, it has to implement the whole PHPUnit framework, right?

Let’s take a closer look at that. The Test Adapter takes advantage of our apps.peachpie.io – a collection of PHP projects compiled to NuGet packages. There are hundreds of packages, that are hidden from the public list (unlisted). One of them is the phpunit/phpunit package itself, and all its dependencies. The Test Adapter simply has a package dependency on this compiled PHP package and invokes PHPUnit’s API internally. This is the NuGet package dependency tree of the PHPUnit Test Adapter:

The package is available on our NuGet feed:

https://feed.peachpie.io/public/v3/index.json

What happens inside?

How does the Test Adapter execute the tests? All the features we mentioned in the article work together – your test application is compiled to a dll file, the PeachPie platform makes it all look like a regular .NET language, the Test Adapter provides the VSTest extension and invokes PHPUnit once it is asked to discover or execute the tests (which are at this point already compiled to .NET, so the .NET debugger has no problems attaching to the process and understanding everything happing inside). The invocation itself is a simple call to the PHPUnit API, in C#:

new Command(ctx).run(new PhpArray(args), exit: true)

For more details, see apps.peachpie.io/Item?id=PHPUnit.TestAdapter.

Conclusion

It is extremely important to test your code, even more so when your code has been translated from PHP to .NET, from one platform to a completely different runtime. Our Test Adapter lets you test your apps within a comfortable and powerful environment, using a fully featured, perfectly supported and well known process. With the .NET platform, PHP testing becomes a natural process.

Posted on November 19, 2020, in category Information, Tutorial, tags: , , , ,