
By Aliaksandr Shabanau, PHP Developer at Attico
If you’re juggling a growing app or wrangling an older Drupal project, testing is supposed to keep things stable. But the truth? Even the most well-meaning test suite can slowly turn into a source of pain, slowing you down, breaking at random, or making every change feel risky.
Sound familiar? It’s more common than you think.
I’ve worked with teams who started out thinking, “Let’s just write a few quick tests to be safe.” Fast-forward a year, and they’re buried under a pile of fragile mocks and confusing test names no one wants to touch.
The good news is, you don’t need to rewrite everything. What you need is a shift in strategy.
In this guide, I’ll walk through ten proven ways to make your unit tests faster, clearer, and easier to live with. From smarter naming to better mocking and running tests in parallel, these are practical tactics we’ve seen work across plain PHP projects and complex Drupal testing environments alike.
Organize your test files to save time
A messy test folder slows everyone down. The fix? Split your tests by type: unit, functional, kernel, and so on.
In a Drupal module, this structure helps devs jump in fast without digging through unrelated files. It’s a small change, but it pays off.
“Even just splitting tests by type makes it way easier to find, fix, or run what you need,” The author says.
Here’s a clean example of that structure, both in raw folders and in an IDE:


Use @group tags to run only what you need
You don’t have to run every test every time. If you’re deep into one feature or racing toward a deploy, that’s overkill.
Add @group annotations like @group unit, @group checkout, or @group slow, and you can run only what matters.
“We’ll often group tests by module or risk level—so if we’re short on time, we can at least run the critical ones,” He advises.
It’s simple. And when the time to deploy hits, being able to run just the essentials can save you serious stress.
Name your tests like you mean it
A good test name tells you what’s being checked, without opening the file or reading the code.
Instead of something vague like testZeroCase, go with testMultiplyingNumberByZeroReturnsZero. It’s clearer, easier to debug, and way friendlier to anyone picking up your code later (including future you).
Test one thing at a time
Trying to check too much in one test just makes debugging harder. If something breaks, you’re left guessing which part failed.
Keep each test focused on one behavior, one outcome. It makes failures clearer, tests easier to read, and your whole suite easier to maintain as the code changes.
This is especially valuable in fast-paced projects like Drupal mobile app development, where clarity and speed go hand-in-hand.
Make sure each test stands on its own
Good tests don’t share state. Each one should run cleanly on its own, in any order, without depending on leftovers from others.
Use setUp() to prepare what the test needs, and tearDown() to reset everything after. That way, every test gets a fresh start—no weird side effects, no hidden dependencies.
“If a test fails, you want to be sure it’s the code—not leftover junk from a previous run,” He often reminds teams.
Here’s a simple example:

Use the “Arrange, Act, Assert” (AAA) pattern
The cleanest tests follow a simple rhythm: prepare, execute, verify.
Arrange: set up the test data
Act: run the code
Assert: check the result
This structure makes your tests easier to read and debug, even months later.
Here’s what it looks like in practice:

Use data providers to test more with less code
When you’re testing the same logic with different inputs—like a mix of valid and invalid emails—don’t write five separate tests. Just use a @dataProvider.
It feeds each input/output pair into the same test method, keeping things clean and easy to maintain.
“If the logic stays the same, there’s no reason to duplicate the whole test—just feed it new data,” The author likes to say.
Here’s what that looks like:

Use mocking to simulate external services
Tests shouldn’t rely on real APIs, file systems, or databases. They’re slow, flaky, and hard to control, which is why mocks are a better choice, says Medium.
A mock is a stand-in. You tell it how to behave, and it does exactly that—no real work, no side effects. It’s perfect for speeding up tests and making them reliable in any environment.
“Mocks let you test your logic without dragging the whole system along for the ride,” He sums up.
Here’s an example that replaces a real logger with a mock during testing:

// Step 1: Define the classes for the example.
/**
* The dependency. This is what we will mock.
* In a real app, this might write to a file, a database, or a web service.
*/
class Logger
{
/**
* Logs a message.
*/
public function log(string $message): void
{
// Imagine this writes to a file. We don’t want our test to do that.
echo “–> REAL LOGGER: Logging ‘{$message}’\n”;
}
}
/**
* The class we want to test.
* It uses the Logger to record its actions.
*/
class Greeter
{
private Logger $logger;
/**
* The Logger is injected as a dependency.
*/
public function __construct(Logger $logger)
{
$this->logger = $logger;
}
/**
* Greets a person and logs the action.
*/
public function sayHello(string $name): string
{
$greeting = “Hello, {$name}!”;
$this->logger->log($greeting);
return $greeting;
}
}
// Step 2: Set up the PHPUnit Test Case.
// This would be in a file like ‘tests/GreeterTest.php’.
use PHPUnit\Framework\TestCase;
/**
* The test class for Greeter.
*/
class GreeterTest extends TestCase
{
/**
* Test that sayHello method logs the greeting.
*/
public function testSayHelloLogsTheGreeting(): void
{
// ARRANGE
// 1. Create a mock of the Logger class.
// This object will stand in for the real Logger.
$mockLogger = $this->createMock(Logger::class);
// 2. Set up an expectation for the mock.
// We expect its ‘log’ method to be called exactly one time.
$mockLogger->expects($this->once())
->method(‘log’)
// We also check that it’s called with the correct greeting.
->with($this->equalTo(‘Hello, Alice!’));
// 3. Create the object we’re testing and give it our mock logger.
$greeter = new Greeter($mockLogger);
// ACT
// Call the method we want to test.
$greeter->sayHello(‘Alice’);
// ASSERT
// In this case, we don’t need a separate assertion like $this->assertTrue().
// The test’s success or failure depends entirely on whether the mock’s
// expectations (set up above) were met. If log() isn’t called, or is called
// with the wrong message, PHPUnit will automatically fail the test.
}
}
Write tests while the code is still fresh
The best time to write tests? Right after the code.
Wait too long, and you’ll forget the tricky parts—or why you handled something a certain way. Then you waste time figuring it all out again.
Writing tests early keeps your thinking clear and your edge cases covered. It’s faster, cheaper, and way less frustrating than coming back to it later.
Run tests in parallel to save time
Slow test suite? Parallel execution can help.
Instead of running tests one at a time, tools like ParaTest let you split the work across multiple CPU cores. That means faster feedback and shorter waits, especially on bigger projects.
If you’ve got hundreds of tests, this tweak alone can save minutes on every run.
Final thoughts
Testing doesn’t have to be a chore. With the right habits—clear names, smart structure, good isolation—your test suite becomes a tool you actually trust. One that helps you move faster, not slower.
But don’t wait until things start to break or slow down. Start now. Tighten up your process, even in small ways. Those little improvements add up.
And if you’re working with Drupal and want to go further with test automation, we’re here to help. Our team works with projects of all sizes to simplify testing and bring clarity to complex systems as part of our quality assurance process.
“The goal isn’t just clean code—it’s peace of mind when you ship,” The author concludes
Because in the end, great tests don’t just protect your code. They protect your time. And that’s worth it.










