
Speed up your testing with Deuteros

January 21, 2026
Take Away:
At Tag1, we believe in proving AI within our own work before recommending it to clients. This post is part of our AI Applied content series, where team members share real stories of how they're using Artificial Intelligence and the insights and lessons they learn along the way. Here, Francesco Placella, Senior Architect | Technical Lead, explores how AI supported his creation of Deuteros, an open‑source PHP library that makes Drupal’s entity system far easier to unit test—speeding up test runs by orders of magnitude and now freely available for anyone to use and contribute to.
Speed up your testing with Deuteros
At Tag1 we like performance.
We also like not being yelled at because a recently released production build introduced a major regression and suddenly the dev team has to scramble to hot-fix the issue.
Our industry has settled for quite a while now on a solid strategy to avoid that scenario: automated testing. It’s not a silver bullet but, when done right, it can hugely increase confidence in making changes to large and/or complex codebases, where even minimal tweaks can sometimes trigger very hard to predict side-effects.
Doing it Right
Automated testing is a vast and complex subject, with some grey areas, especially around terminology; however, there’s a key principle it’s a good idea to stick to: always run your automated tests after any change.
If you are embracing Test Driven Development, you’ll have written new tests covering the changes you are working on before implementing them: this will require running tests multiple times before the task is over. However, even if you are following a more traditional approach, writing test coverage after implementing changes, you will still end up running tests very often.
Admittedly, in the cases above we are talking about a very limited set of tests; however, if tests run slowly, even those will add a significant overhead to development time.
Another widely accepted best practice is to run the entire test suite before merging a change into the project’s repository. This both ensures that the new code behaves as expected and that no regression was introduced in the process. Once again, if tests run slowly, it may take a long time to get feedback, adding further overhead due to context switching.
Aside from the impact on developer experience, which at Tag1 is taken very seriously, the larger the overhead, the harder it is to justify to clients the amount of time spent implementing a change. I guess I don’t need to describe the cascading effects.
The Test Pyramid
An approach that’s very effective in addressing test speed issues is the test pyramid: without going too much into detail, the idea is that a test suite should be organized in a pyramidal shape with multiple layers getting smaller and slower to run as they approach the top.
This commonly translates into having:
-
A large amount of fast-running unit tests, covering individual aspects of the codebase.
-
A medium amount of integration tests combining multiple units into a consistent (sub)system and testing its behavior.
-
A small amount of end-to-end (E2E) tests pulling everything together and testing the business-critical behaviors of the entire codebase.

Figure 1: The Test Pyramid
What about Drupal?
At Tag1 we do many things, but Drupal has a special place in our hearts. However, running Drupal tests quickly has often been challenging.
Support for automated tests was initially added to Drupal 7 and, as awesome as it was at the time, it involved running a full site installation in the test setup phase before being able to perform a single assertion. The Simpletest framework focused mainly on UI testing and, even if it was sort of possible to use it to write unit tests, it was like driving to your favorite movie theater during rush hour just to check whether the car battery was wired properly. As you can imagine, it took time.
In Drupal 8 and above the situation vastly improved with the adoption of the PhpUnit testing framework. Multiple types of tests were introduced:
- Browser tests (and their companion WebDriver tests) are the successors to the original Simpletests and are primarily meant to perform UI testing. These are a very good fit to implement E2E tests, as they cover the full request/response scope, albeit being slow, as they still require an installed site and a full Drupal bootstrap.
It should be noted that the introduction by Moshe Weitzman of Drupal Test Traits helped to speed up these tests, as it allows them to run with an existing database, instead of installing an empty site from scratch every time.
-
Kernel tests run much faster, as they only require installing the subsystems needed to run the test code. These are ideal for Integration testing, as they allow you to pick and choose the parts of Drupal available to the test.
-
Unit tests, lightning-fast and narrow-scoped, are what should be used to implement the large majority of the test coverage. By default, they do close to nothing in the setup phase, allowing to test individual codebase units in isolation, by providing test doubles of all the objects the test subject interacts with.
So we are good now, right? Well, not exactly. The entity subsystem – which powers users, nodes, media items, and taxonomy terms among others – has historically been problematic when it comes to unit testing, because instantiating an entity object involves instantiating a significant amount of dependencies. In most cases this meant that, to write an entity unit test, a Kernel test with all its baggage was needed. Since entities are very prevalent in any Drupal codebase, this meant that a wide range of test scenarios could not rely on fast unit tests.
Meet Deuteros
This issue has been bugging me for a long time now, so I decided to experiment with an AI-assisted solution since long and tedious tasks is something LLMs are supposed to be good at.
The result is Deuteros – Drupal Entity Unit Test Extensible Replacement Object Scaffolding – a PHP library allowing the use of fast unit tests (extending UnitTestCase, for those in the know) when interacting with entities. And, yes, Claude helped me come up with the name as well.
Deuteros is fully open source and available today. You can explore the project, file issues, or contribute improvements on GitHub. The repository includes examples, docs, and instructions to start using it right away.
This is achieved by:
- Allowing the creation of test doubles implementing various entity and field interfaces. These can be passed around in test code as if they were real entity objects.
use Deuteros\Double\EntityDoubleFactory;
use Deuteros\Double\EntityDoubleDefinitionBuilder;
class MyServiceTest extends TestCase {
public function testMyService(): void {
// Get a factory (auto-detects PHPUnit or Prophecy)
$factory = EntityDoubleFactory::fromTest($this);
// Create an entity double
$entity = $factory->create(
EntityDoubleDefinitionBuilder::create('node')
->bundle('article')
->id(42)
->label('Test Article')
->field('field_body', 'Article content here')
->build()
);
// Use it in your test
$myService = new MyService();
$myService->doStuff($entity);
// These assertions would all pass
$this->assertInstanceOf(EntityInterface::class, $entity);
$this->assertSame('node', $entity->getEntityTypeId());
$this->assertSame('article', $entity->bundle());
$this->assertSame(42, $entity->id());
$this->assertSame('Article content here', $entity->get('field_body')->value);
}
}
- Allowing the instantiation of real entity objects while providing test doubles for dependencies such as field items and Entity Field API services. This allows unit testing of custom entity logic such as the one typically added to bundle classes.
class MyNodeTest extends TestCase {
private SubjectEntityFactory $factory;
protected function setUp(): void {
$this->factory = SubjectEntityFactory::fromTest($this);
$this->factory->installContainer();
}
protected function tearDown(): void {
$this->factory->uninstallContainer();
}
public function testNodeCreation(): void {
$node = $this->factory->create(Node::class, [
'nid' => 42,
'type' => 'article',
'title' => 'Test Article',
'body' => ['value' => 'Body text', 'format' => 'basic_html'],
'status' => 1,
]);
// Real Node instance with DEUTEROS field doubles.
$this->assertInstanceOf(Node::class, $node);
$this->assertSame('node', $node->getEntityTypeId());
$this->assertSame('article', $node->bundle());
$this->assertSame('Test Article', $node->get('title')->value);
$this->assertSame('Body text', $node->get('body')->value);
}
}
How AI Enabled a New, Faster Class of Drupal Tests
This sounds all good but does it actually work? Well, the library is already being adopted by some Tag1 client projects and it seems to be working nicely. To get an idea of the speed gains Deuteros allows, we implemented some benchmark tests: a simple test trait performing a few node manipulation operations was used to measure the performance of multiple test types: in my DDEV local environment, 1000 iterations of the test complete in less than 1 second with Unit tests while they take ~12 minutes with Kernel tests – details and replication steps available here. Of course, running tests in parallel could likely reduce the gap by a factor of ten, but I’ll still take the former, thanks.
What’s Next?
Complex projects very often require patches to implement new functionality not yet available in the Drupal codebase, or apply bug fixes before they are eventually released. Applying patches for all intents and purposes means running a fork of the original project, which means there is no guarantee that, even if individual patches don’t break tests, their combined behavior won’t introduce regressions. Ideally, to be fully confident about the codebase quality, one would need to run the entire Drupal core + contrib test suite and confirm that nothing breaks. Currently, even on the drupal.org infrastructure – which is fully optimized for the job – on average you need to wait for ~15 minutes to get test results: adding the test suites of a hundred modules increases that by a lot. Running this on every pull request or commit push does not seem viable at the moment.
Deuteros was written as a standalone PHP library, so that it could easily be adopted without having to make significant changes to the host project. If it were widely adopted both in Drupal core and in contrib-land, running all unit test suites could probably happen in less than one minute, increasing the confidence in our changes by a lot, and possibly reducing our energy footprint as a bonus. Should we try?
Image by Google DeepMind from pexels.