Opened 5 years ago
Closed 4 years ago
#53364 closed task (blessed) (fixed)
Tests: Reduce usage of assertEquals for 5.9
| Reported by: |
|
Owned by: | |
|---|---|---|---|
| Milestone: | 5.9 | Priority: | normal |
| Severity: | normal | Version: | |
| Component: | Build/Test Tools | Keywords: | php8 has-patch has-unit-tests |
| Focuses: | Cc: |
Description
Follow-up to:
The assertEquals() test method does not check that the types of the expected and actual values match. This can hide subtle bugs especially when the values are falsey.
Tasks:
- Switch to using
assertSame()when the type of the value in the assertion is important - Replace overall usage of
assertEquals()with type-strict assertion methods, with the aim of potentially removing its usage altogether
Change History (8)
This ticket was mentioned in PR #1768 on WordPress/wordpress-develop by costdev.
4 years ago
#1
- Keywords has-patch has-unit-tests added; needs-unit-tests removed
#2
@
4 years ago
Overview
I've taken a closer look at the test suite and usage of assertEquals(). My thoughts on the stages to take are below.
Stage 1:
'Clean' replacements.
PR 1768 replaces some instances of assertEquals() with assertSame(). More specifically, it makes replacements that appear to be 'clean' replacements, requiring no adjustment of Tests or Core code. All PHPUnit tests pass.
Stage 2:
I have looked at the cases where the returned value is float rather than int.
As @SergeyBiryukov noted here:
In other cases, this points to minor bugs in core, e.g. using
ceil()for values that are documented as integers, but without explicitly casting toint, results in them being of typefloatinstead. This affects some properties likemax_num_pages,max_pages,total_pagesin various classes, or functions likeget_comment_pages_count(),wp_embed_defaults(),get_oembed_response_data().
A related ticket further down has a lengthy discussion about ways to resolve this.
- I've tested one of the proposed solutions, a simple cast to
intusing(int)before the calls toceil(), followed by changingassertEquals()toassertSame()for relevant tests. All PHPUnit tests pass. - With coverage incomplete this could, theoretically, be a breaking change. However, we are yet to come across a real-world example of this, or even an example scenario where these methods should return a
float.
Stage 3
I have looked at the cases where the returned value is string rather than int.
After investigation, the actual values are a numeric string for compatibility reasons and are documented as such. For this reason, the expected value should be a numeric string, rather than changing the code in src and breaking compatibility.
Stage 4
The returned value is array and assertEquals() is being used instead of assertSameSets(). It should be noted that an array containing an object will not return the same signature, thus cannot be tested using assertSameSets() or assertSameSetsWithIndex().
Stage 5
The returned value is always bool rather than int. In these cases, the expected value should be changed to a bool.
This can be done directly e.g. 0 becomes false, or by conditional if comparing variables, e.g. instead of comparing $some_int, compare the result of $expected = ( 0 === $some_int ).
This one requires particularly close attention to the methods being tested, their possible return values and the accuracy of the current tests.
Stage 6
The returned value is float rather than int. This requires investigation to determine why the value is float and whether this is correct, should be changed in src, or must remain float for compatibility reasons, thus the expected value in the tests should be changed to float.
Stage 7
N.B. This may go outside the scope of this ticket.
The returned value is array and assertEqualSets() or assertEqualSetsWithIndex() is being used instead of assertSameSets() and assertSameSetsWithIndex() respectively. It should be noted that an array containing an object will not return the same signature, thus cannot be tested using assertSameSets() or assertSameSetsWithIndex().
#3
@
4 years ago
Report
Stage 0
Changes: None
Results for assertEquals(): 528 results in 118 files. ( -0 )
Stage 1
Changes:
- PR 1768 - Replace
assertEquals()withassertSame()where tests pass with no other code requiring changes.
Results for assertEquals(): 519 results in 118 files. ( -9 )
Status: The PR is ready for review.
Stage 2
Changes:
- All of the above (for calculations, not for the patch)
- Cast
ceil()tointthroughout non-third party.phpfiles insrc. - Change
assertEquals()toassertSame()for relevant tests.
Results for assertEquals(): 493 results in 115 files. ( -26 )
Notes:
- Remaining tests that compare an
intto afloatusingassertEquals()are not affected by theceil()change. As a result, these have been left until Stage 6 for investigation.
Status:
- Pending feedback on the approach.
- The patch is prepared and with some self-review, will be ready for PR.
Stage 3
Changes:
- All of the above (for calculations, not for the patch)
- For most assertions that compare a raw
intto astringresponse, wrap the expected value in''. - For some in
tests/phpunit/tests/image/meta.php, these are asserting camera shutter speed in EXIF data. These have been casted tostringusing, for example,(string) ( 1 / 40 ).
Results for assertEquals(): 402 results in 112 files. ( -91 )
Status:
- Pending feedback on the approach.
- The patch is prepared and with some self-review, will be ready for PR.
This ticket was mentioned in Slack in #core by costdev. View the logs.
4 years ago
#6
follow-up:
↓ 7
@
4 years ago
Update
The PR currently reduces assertEquals() usage from ~558 to ~191 through various changes to assertions. There are currently no changes in source.
Regarding the remaining ~191:
As we know, many of them are comparing objects, so using assertEquals() is fine. But, it's a pain to review those every release to make sure that we're not missing anything. It also gives us a false perception of the estimated number of unacceptable uses of assertEquals() for us to deal with for that release.
What about two additional assertions that are just aliases of assertEquals()?
// For two objects.
// We can't strictly compare objects in most cases anyway,
// so this name should be safe and the implied comparison
// is accurate in all but object id.
public function assertSameObjects( $expected, $actual, $message = '' ) {
$this->assertEquals( $expected, $actual, $message );
}
// For arrays containing objects.
public function assertSameSetsWithObjects( $expected, $actual, $message = '' ) {
$this->assertEquals( $expected, $actual, $message );
}
The intended benefit is a clearer picture of possible loose comparison with assertEquals() by excluding known acceptable usage. There's also a sort of maybe benefit that by offering the assertion, it reinforces the idea of stricter testing. This would reduce the remaining ~191 by quite a bit and minimize the work involved with each release.
For the remainder, these are largely due to float being compared to int. For nearly all of these, this is an issue with ceil(). It has been suggested before that we can cast the result of ceil() to int to remedy this when the value is documented as an int.
So, a call for opinions:
- Should we add the two aliases to make our lives easier going forward?
- Should we cast
ceil()tointwhere appropriate to help us implement stricter comparison?
#7
in reply to:
↑ 6
@
4 years ago
Replying to costdev:
What about two additional assertions that are just aliases of
assertEquals()?
// For two objects. // We can't strictly compare objects in most cases anyway, // so this name should be safe and the implied comparison // is accurate in all but object id. public function assertSameObjects( $expected, $actual, $message = '' ) { $this->assertEquals( $expected, $actual, $message ); } // For arrays containing objects. public function assertSameSetsWithObjects( $expected, $actual, $message = '' ) { $this->assertEquals( $expected, $actual, $message ); }
This sounds like a great idea to me!
So, a call for opinions:
- Should we add the two aliases to make our lives easier going forward?
- Should we cast
ceil()tointwhere appropriate to help us implement stricter comparison?
I'd say yes to both.
This ensures that not only the return values match the expected results, but also that their type is the same.
Going forward, stricter type checking by using
assertSame()should generally be preferred toassertEquals()where appropriate, to make the tests more reliable.