PHP
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Functions
-
Section 4: Object-Oriented PHP
-
Section 5: Working with Data
-
Section 6: Modern PHP (PHP 8)
-
Section 7: Working with Files and Networking
-
Section 8: Common PHP Frameworks Overview
-
Section 9: Tooling and Ecosystem
-
Section 10: Practical Projects
-
Section 11: Interview Practice
-
Section 12: More Standard Library
-
Section 13: Data Structures and Algorithms in PHP
-
Section 14: More Practice Exercises
-
Section 15: More Security and Best Practices
-
Section 16: More Testing and Tooling
-
Section 17: WordPress-Style CMS Concepts
-
Section 18: Advanced OOP Practice
-
Section 19: More Web Fundamentals
-
Section 20: Database Practice
-
Section 21: PHP Manual: Array Functions
-
Section 22: PHP Manual: Date and Calendar Functions
-
Section 23: PHP Manual: Filesystem and Directory Functions
-
Section 24: PHP Manual: Filter and Var Handling
-
Section 25: PHP Manual: Math Functions
-
Section 26: PHP Manual: JSON and XML
-
Section 27: PHP Manual: Network and Stream Functions
-
Section 28: PHP Manual: Error and Exception Handling
-
Section 29: PHP Manual: Output Control and Misc
-
Section 30: PHP Manual: FTP, Zip, and Mail
-
Section 31: Modern PHP Frameworks Deep Dive
-
Section 32: PHP Design Patterns
-
Section 33: More Practice Exercises
-
Section 34: PHP Performance and Deployment
-
Section 35: More Interview Practice
-
Section 36: More PHP Standard Library
-
Section 37: PHP Concurrency and Async
-
Section 38: More Web Development Practice
-
Section 39: PHP Testing Deep Dive
-
Section 40: Composer and Package Development
-
Section 41: PHP Security Deep Dive
-
Section 42: More Practical Projects
-
Section 43: Legacy PHP Maintenance
-
Section 44: More Algorithm Practice
-
Section 45: Final Practice and Review
-
Section 46: PHP for E-Commerce Patterns
-
Section 47: PHP API Design Deep Dive
-
Section 48: PHP Caching Strategies
-
Section 49: PHP Queue and Background Jobs
-
Section 50: PHP Multi-Tenancy Patterns
-
Section 51: PHP Real-Time Features
-
Section 52: PHP CMS and Content Modeling
-
Section 53: PHP Internationalization
-
Section 54: More Framework-Specific Practice
-
Section 55: PHP Legacy Code Refactoring
-
Section 56: More Practice Projects Round 2
-
Section 57: PHP Command-Line Applications
-
Section 58: PHP and Microservices
-
Section 59: More Interview and Review Round 2
168: Code Coverage Reports with PHPUnit
By now, you're probably comfortable writing tests. You know that when the green bar appears, your code is doing what you expected. But there's a lingering question that always haunts me after a long coding session: What did I forget to test?
That's where code coverage comes in. It doesn't tell you if your code is "correct"—it just tells you which lines of your code were actually executed during your test suite. It's a map of your blind spots.
Building a Discount Calculator with some blind spots
Let's build a small DiscountCalculator. This is a classic scenario because business logic usually involves a lot of "if this, then that" branches, which are exactly where bugs hide.
<?php
namespace App;
class DiscountCalculator {
public function applyDiscount(float $total, string $customerType): float {
if ($customerType === 'VIP') {
return $total * 0.80; // 20% off
}
if ($total > 100) {
return $total * 0.90; // 10% off for big spenders
}
if ($customerType === 'NEW') {
return $total * 0.95; // 5% off for new users
}
return $total;
}
}
Now, I'll write a quick test. I'm feeling lazy, so I'm only going to test the VIP scenario and the default case. I'm intentionally ignoring the "big spender" and "new user" logic.
<?php
use PHPUnit\Framework\TestCase;
use App\DiscountCalculator;
class DiscountCalculatorTest extends TestCase {
public function testVipDiscount() {
$calc = new DiscountCalculator();
$this->assertEquals(80, $calc->applyDiscount(100, 'VIP'));
}
public function testNoDiscount() {
$calc = new DiscountCalculator();
$this->assertEquals(50, $calc->applyDiscount(50, 'REGULAR'));
}
}
Generating the first coverage report
If I run phpunit now, everything passes. I might feel great, thinking my code is fully tested. But let's run it with the coverage flag. Assuming you have Xdebug or PCOV installed (you'll need one of these for coverage to work), I'll run:
vendor/bin/phpunit --coverage-html reports
This tells PHPUnit to analyze the execution and dump a pretty HTML report into a folder called reports. When I open index.html in my browser, I'll see a percentage. It won't be 100%. If I click through to the DiscountCalculator file, I'll see the VIP and default return lines highlighted in green, but the "big spender" and "new user" blocks highlighted in red.
The "Empty Report" mistake
Here's something that happened to me recently. I ran the coverage command on a new project and got a report that claimed 0% coverage, even though my tests were passing. I spent ten minutes questioning my sanity before I realized the problem: I hadn't told PHPUnit which files should actually be tracked.
By default, PHPUnit doesn't just guess which folders contain your source code. I had to add a block to my phpunit.xml file. If you see an empty report, check your config. It should look something like this:
<phpunit>
<source>
<include>
<directory suffix=".php">src</directory>
</include>
</source>
</phpunit>
Once I added that, the report immediately populated. Lesson learned: always check your phpunit.xml before blaming the tool.
Closing the gaps
Now that the report has pointed out exactly where I'm vulnerable, I can write targeted tests to hit those red lines. I don't have to guess; I just follow the map.
public function testBigSpenderDiscount() {
$calc = new DiscountCalculator();
$this->assertEquals(180, $calc->applyDiscount(200, 'REGULAR'));
}
public function testNewCustomerDiscount() {
$calc = new DiscountCalculator();
$this->assertEquals(95, $calc->applyDiscount(100, 'NEW'));
}
Running the report again, those red blocks turn green. We've hit 100% coverage. Keep in mind, this doesn't mean the code is bug-free—it just means every line was executed at least once. You still need to think about edge cases, like negative totals or empty strings, but coverage ensures you didn't simply forget a whole chunk of logic.
📋 Practical Task
Achieving Full Coverage for the OrderTaxCalculator
You have been handed a OrderTaxCalculator class that handles different tax rates based on the region (USA, EU, and Others). However, the previous developer left the project without writing a full test suite.
Your Task:
- Create the
OrderTaxCalculatorclass with the following logic:- If region is 'USA', tax is 10%.
- If region is 'EU', tax is 20%.
- If the order total is over 1000 regardless of region, apply an additional 'luxury tax' of 5% on top of the regional tax.
- For any other region, tax is 5%.
- Write a PHPUnit test class. Initially, only write tests for the 'USA' and 'EU' basic rates.
- Configure your
phpunit.xmlto include your source directory. - Run the code coverage report (
--coverage-html reports) and identify which lines are uncovered (the luxury tax logic and the 'Other' region logic). - Write the remaining tests until your coverage report shows 100% for the
OrderTaxCalculatorclass.
There are no comments for now.