4.3 Testing and Code Quality
Code quality encompasses both automated testing to verify functionality and consistent coding standards that ensure maintainability. We approach both aspects systematically, using industry-standard tools and practices that scale across team members and projects.
4.3.1 Automated Testing
We write testable code by following SOLID principles and using dependency injection to make our classes easier to test in isolation. Both WordPress and Laravel projects should include automated tests for critical functionality, with test coverage focused on business logic, integrations, and user-facing features.
4.3.2 Unit and Integration Testing
For WordPress projects, we combine PHPUnit with WordPress’s testing framework to test custom functionality within the WordPress environment. This approach allows us to test database interactions, hook systems, and WordPress-specific functionality reliably.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?php
// WordPress integration test example
class ContactFormTest extends WP_UnitTestCase {
public function setUp(): void {
parent::setUp();
// Create test user and posts as needed
$this->user_id = $this->factory()->user->create(['role' => 'editor']);
}
public function test_contact_form_saves_submission() {
$form_data = [
'name' => 'Test User',
'email' => 'test@example.com',
'message' => 'This is a test message'
];
$handler = new ContactFormHandler(new FormValidator(), new DatabaseLogger());
$result = $handler->handleSubmission($form_data);
$this->assertTrue($result['success']);
// Verify data was saved to database
$submissions = get_posts(['post_type' => 'contact_submission']);
$this->assertCount(1, $submissions);
}
public function test_contact_form_sends_notification() {
$mailer = new TestMailer();
$handler = new ContactFormHandler(new FormValidator(), $mailer);
$form_data = [
'name' => 'Test User',
'email' => 'test@example.com',
'message' => 'Test message'
];
$handler->handleSubmission($form_data);
$this->assertTrue($mailer->wasEmailSent());
$this->assertContains('test@example.com', $mailer->getLastEmail()['to']);
}
}
Laravel provides excellent testing tools out of the box, including database factories, seeders, and both unit and feature testing capabilities. Feature tests verify complete user workflows, while unit tests focus on individual class methods.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php
// Laravel feature test example
class ContactFormTest extends TestCase {
use RefreshDatabase;
public function test_contact_form_submission_creates_record() {
$formData = [
'name' => 'Test User',
'email' => 'test@example.com',
'message' => 'This is a test message'
];
$response = $this->post('/contact', $formData);
$response->assertStatus(302);
$response->assertSessionHas('success');
$this->assertDatabaseHas('contact_submissions', [
'email' => 'test@example.com',
'name' => 'Test User'
]);
}
public function test_contact_form_validation_errors() {
$response = $this->post('/contact', [
'name' => '',
'email' => 'invalid-email',
'message' => ''
]);
$response->assertSessionHasErrors(['name', 'email', 'message']);
}
}
4.3.3 Browser Testing
Laravel Dusk provides browser automation testing for complex user interactions that can’t be adequately tested through feature tests alone. We use Dusk for testing JavaScript-heavy features, multi-step workflows, and cross-browser compatibility.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<?php
// Laravel Dusk browser test
class ContactFormBrowserTest extends DuskTestCase {
public function test_contact_form_javascript_validation() {
$this->browse(function (Browser $browser) {
$browser->visit('/contact')
->type('name', 'Test User')
->type('email', 'invalid-email')
->press('Submit')
->waitForText('Please enter a valid email address')
->assertSee('Please enter a valid email address');
});
}
public function test_contact_form_successful_submission() {
$this->browse(function (Browser $browser) {
$browser->visit('/contact')
->type('name', 'Test User')
->type('email', 'test@example.com')
->type('message', 'This is a test message')
->press('Submit')
->waitForText('Thank you for your message')
->assertSee('Thank you for your message');
});
}
}
4.3.4 Coding Standards
Consistent code formatting and style improve readability and reduce friction during code reviews. We use automated tools to enforce standards and catch common issues before human review.
4.3.4.1 PSR-12 Foundation
Our PHP projects follow PSR-12 as the base coding standard, with additional rules specific to WordPress development when applicable. This approach provides consistency across our Laravel projects while accommodating WordPress’s established conventions where necessary.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!-- phpcs.xml configuration for Laravel projects -->
<?xml version="1.0"?>
<ruleset name="PSR12 Laravel Standards">
<!-- Base PSR-12 standard -->
<rule ref="PSR12"/>
<!-- Additional rules for better code quality -->
<rule ref="Generic.Arrays.DisallowLongArraySyntax"/>
<rule ref="Generic.PHP.ForbiddenFunctions">
<properties>
<property name="forbiddenFunctions" type="array">
<element key="dd" value="null"/>
<element key="dump" value="null"/>
<element key="var_dump" value="null"/>
</property>
</properties>
</rule>
<!-- Include specific directories -->
<file>app/</file>
<file>database/</file>
<file>routes/</file>
<file>tests/</file>
<!-- Exclude vendor and generated files -->
<exclude-pattern>vendor/</exclude-pattern>
<exclude-pattern>bootstrap/cache/</exclude-pattern>
</ruleset>
4.3.4.2 WordPress-Specific Standards Integration
WordPress projects require a hybrid approach that respects WordPress coding conventions while maintaining modern PHP standards where possible. We create custom rulesets that combine WordPress standards with selected PSR-12 rules.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
<?xml version="1.0"?>
<ruleset name="crowdfavorite">
<description>PSR-12 based WordPress coding standard.</description>
<!-- Increase maximum memory for WordPress. -->
<ini name="memory_limit" value="256M"/>
<!-- Only ever scan these directories -->
<file>/pub</file>
<!-- Ignore files/directories that match these patterns -->
<exclude-pattern>*/build/*</exclude-pattern>
<exclude-pattern>*/twenty*/*</exclude-pattern>
<exclude-pattern>*/assets/dist/*</exclude-pattern>
<exclude-pattern>*/vendor/*</exclude-pattern>
<exclude-pattern>*/tests/*</exclude-pattern>
<exclude-pattern>*/cache/*</exclude-pattern>
<exclude-pattern>*/autoload.php</exclude-pattern>
<exclude-pattern>*/node_modules/*</exclude-pattern>
<!-- Scan only PHP files -->
<arg name="extensions" value="php"/>
<!-- Show colors in console -->
<arg value="-colors"/>
<!-- Show sniff codes in all reports -->
<arg value="ns"/>
<!-- Ignore long lines -->
<rule ref="PSR12">
<exclude name="Generic.Files.LineLength.TooLong" />
</rule>
<!-- WordPress Security Rules (non-conflicting with PSR-12) -->
<rule ref="WordPress.Security.EscapeOutput"/>
<rule ref="WordPress.Security.NonceVerification"/>
<rule ref="WordPress.Security.ValidatedSanitizedInput"/>
<rule ref="WordPress.Security.PluginMenuSlug"/>
<rule ref="WordPress.Security.SafeRedirect"/>
<!-- WordPress Database Rules -->
<rule ref="WordPress.DB.DirectDatabaseQuery"/>
<rule ref="WordPress.DB.PreparedSQL"/>
<rule ref="WordPress.DB.RestrictedFunctions"/>
<rule ref="WordPress.DB.RestrictedClasses"/>
<rule ref="WordPress.DB.SlowDBQuery"/>
<!-- WordPress WP-specific Rules -->
<rule ref="WordPress.WP.AlternativeFunctions"/>
<rule ref="WordPress.WP.DeprecatedFunctions"/>
<rule ref="WordPress.WP.DiscouragedFunctions"/>
<rule ref="WordPress.WP.GlobalVariablesOverride"/>
<rule ref="WordPress.WP.I18n" text_domain="disney-rewards" />
<rule ref="WordPress.WP.EnqueuedResources"/>
<rule ref="WordPress.WP.CronInterval"/>
<!-- WordPress PHP Rules (keep Yoda conditions since PSR12 does not specify) -->
<rule ref="WordPress.PHP.YodaConditions"/>
<rule ref="WordPress.PHP.DevelopmentFunctions"/>
<rule ref="WordPress.PHP.DiscouragedPHPFunctions"/>
<rule ref="WordPress.PHP.IniSet"/>
<rule ref="WordPress.PHP.POSIXFunctions"/>
<rule ref="WordPress.PHP.RestrictedPHPFunctions"/>
<!-- WordPress PHP Compatibility -->
<rule ref="PHPCompatibilityWP" />
<!-- Set minimum PHP version -->
<config name="testVersion" value="8.2-"/>
<!-- Exclude WordPress rules that conflict with PSR-12 -->
<!-- Exclude WordPress indentation rules (use PSR-12 + tabs) -->
<rule ref="WordPress.WhiteSpace.ControlStructureSpacing">
<exclude name="WordPress.WhiteSpace.ControlStructureSpacing.NoSpaceAfterOpenParenthesis"/>
<exclude name="WordPress.WhiteSpace.ControlStructureSpacing.NoSpaceBeforeCloseParenthesis"/>
</rule>
<!-- Exclude WordPress file naming (use PSR-4/CamelCase) -->
<rule ref="WordPress.Files.FileName">
<severity>0</severity>
</rule>
<!-- Exclude WordPress array formatting (PSR-12 allows short arrays) -->
<rule ref="WordPress.Arrays.ArrayDeclarationSpacing">
<severity>0</severity>
</rule>
<!-- Exclude WordPress brace style (use PSR-12 same-line braces) -->
<rule ref="WordPress.WhiteSpace.ControlStructureSpacing">
<exclude name="WordPress.WhiteSpace.ControlStructureSpacing.BlankLineAfterEnd"/>
</rule>
<!-- Exclude WordPress operator spacing that conflicts with PSR-12 -->
<rule ref="WordPress.WhiteSpace.OperatorSpacing">
<exclude name="WordPress.WhiteSpace.OperatorSpacing.NoSpaceAfter"/>
<exclude name="WordPress.WhiteSpace.OperatorSpacing.NoSpaceBefore"/>
</rule>
<!-- Additional PSR-12 specific rules to ensure compliance -->
<rule ref="PSR2.Classes.PropertyDeclaration"/>
<rule ref="PSR2.Methods.MethodDeclaration"/>
<rule ref="PSR12.Classes.AnonClassDeclaration"/>
<rule ref="PSR12.Classes.ClassInstantiation"/>
<rule ref="PSR12.Classes.OpeningBraceSpace"/>
<rule ref="PSR12.Functions.ReturnTypeDeclaration"/>
<rule ref="PSR12.Functions.NullableTypeDeclaration"/>
<rule ref="PSR12.Keywords.ShortFormTypeKeywords"/>
<rule ref="PSR12.Operators.OperatorSpacing"/>
<rule ref="PSR12.Properties.ConstantVisibility"/>
<!-- Allow modern PHP syntax -->
<rule ref="Generic.Arrays.DisallowLongArraySyntax"/>
<!-- Ensure warnings are shown -->
<arg name="severity" value="1"/>
<arg name="warning-severity" value="1"/>
</ruleset>
4.3.4.3 PSR-4 Autoloading
Modern PHP development relies on PSR-4 autoloading for efficient class loading and namespace organization. Our projects use Composer’s autoloading capabilities to ensure classes are loaded only when needed and follow predictable naming conventions.
Laravel Projects
Laravel uses PSR-4 autoloading by default, and we organize our application code following the framework’s conventions while extending the autoloader for custom namespaces when needed.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
{
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/"
},
"files": [
"app/helpers.php"
]
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
}
}
WordPress Projects with PSR4
WordPress doesn’t use PSR-4 autoloading natively, but our custom themes and plugins implement it through Composer to take advantage of modern PHP development practices.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"autoload": {
"psr-4": {
"CrowdFavorite\\ProjectName\\": "src/",
"CrowdFavorite\\ProjectName\\Admin\\": "src/Admin/",
"CrowdFavorite\\ProjectName\\Frontend\\": "src/Frontend/"
}
},
"autoload-dev": {
"psr-4": {
"CrowdFavorite\\ProjectName\\Tests\\": "tests/"
}
}
}
This approach allows us to write modern, testable PHP code within WordPress projects while maintaining compatibility with WordPress conventions where necessary.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php
// Modern WordPress plugin structure with PSR-4 autoloading
namespace CrowdFavorite\ProjectName;
class Plugin {
private $admin;
private $frontend;
public function __construct(Admin\Settings $admin, Frontend\ShortcodeHandler $frontend) {
$this->admin = $admin;
$this->frontend = $frontend;
}
public function init(): void {
add_action('admin_init', [$this->admin, 'registerSettings']);
add_action('init', [$this->frontend, 'registerShortcodes']);
}
}
// In the main plugin file
require_once __DIR__ . '/vendor/autoload.php';
$plugin = new CrowdFavorite\ProjectName\Plugin(
new CrowdFavorite\ProjectName\Admin\Settings(),
new CrowdFavorite\ProjectName\Frontend\ShortcodeHandler()
);
$plugin->init();