Problem/Motivation

Originally in this issue, trying to make a PHPUnit coverage report, PHPUnit complained. Some fixes were started here, but in the meantime, #2358657: Wrong @covers definitions in Drupal project did the fixes necessary to get the coverage report to run.

While working on this issue, a bug: #2364467: SpecialAttributesRouteSubscriber::onAlterRoutes() doesn't return a value was found.

This issue makes the coverage report more accurate.

Allowed in beta?

https://www.drupal.org/core/beta-changes

This is a bug, because some of the @covers are wrong or missing, and the coverage report is misleading (wrong).
This is normal, because it effects just one system (phpunit, specifically coverage information).

This is allowed in the beta, since this effects only automatic tests, and that type of change is unfrozen.

Proposed resolution

make @covers more accurate, using --strict as a guideline, and correct the name of a data provider (so it doesn't appear as a test)

Remaining tasks

(done) Fix stuff.
(done) Fix more stuff.
(done) Rescope issue.
Commit changes.

User interface changes

No.

API changes

No.

Comments

mile23’s picture

Here's a patch.

Most controversial: CacheTest::testInvalidateTags() actually tested deleteTags(), probably due to copypasta from testDeleteTags()

Removes @covers for non-existent methods, adds @covers in a few files I was touching anyway.

Added assertions to test methods without them (where --strict complained).

mile23’s picture

Status: Active » Needs review
yesct’s picture

  1. +++ b/core/tests/Drupal/Tests/Core/Cache/CacheTest.php
    @@ -127,13 +128,13 @@ public function testDeleteTags() {
       public function testInvalidateTags() {
    -    Cache::deleteTags(['node' => [2, 3, 5, 8, 13]]);
    +    Cache::invalidateTags(['node' => [2, 3, 5, 8, 13]]);
    

    ah, this is the "controversial" thing you mentioned.

  2. +++ b/core/tests/Drupal/Tests/Core/Entity/BaseFieldDefinitionTest.php
    @@ -46,7 +46,7 @@ protected function setUp() {
           'storage_settings' => array(
    -        'some_setting' => 'value 1'
    +        'some_setting' => 'value 1',
    

    this looks out of scope.

the rest of this looks ok to me (but I didn't run the coverage to see the diff in complaints before and after the patch)

mile23’s picture

Yah, item 1 I mentioned in comment 1. Item 2 is coding standards, just a comma. if it's a comma too far I can take it out.

mile23’s picture

@YesCT: I didn't run the coverage to see the diff in complaints before and after the patch

Ya know... we *could* make the testbot do that for us. :-)

mile23’s picture

StatusFileSize
new20.75 KB
new552 bytes

Here it is without the Cache::invalidateTags() change. I'll move that to the other issue.

tim.plunkett’s picture

yesct’s picture

Status: Needs review » Needs work

this is a great issue. thanks for filing it. and for working on fixing it.

@tim.plunkett yeah, this patch takes care of that.

I have some questions and feedback (and I documented how I tested/reviewed it).

1
./vendor/phpunit/phpunit/phpunit --filter=ResponsiveImageMappingConfigEntityUnitTest --coverage-html /tmp/report

in head, without this patch:
.Trying to @cover or @use not existing method "\Drupal\responsive_image\Entity\ResponsiveImageMapping::hasMapping".

with the patch:

.......

Time: 55.89 seconds, Memory: 135.00Mb

OK (7 tests, 17 assertions)

2
./vendor/phpunit/phpunit/phpunit --filter=PermissionHandlerTest --coverage-html /tmp/report
before: Trying to @cover or @use not existing method "\Drupal\user\PermissionHandler::buildPermissions".
after: finishes

I dont think there ever was a buildPermissions in PermissionsHandler (there is one in NodePermissions).

3.
./vendor/phpunit/phpunit/phpunit --filter=CacheTest --coverage-html /tmp/report
before: Trying to @cover or @use not existing class or interface "validateTags".
after: finishes

phpstorm complains that a void menthod is having it's result used.
but asserting something seems better than relying on not an exception.

the other changes in here are good (adding :: to before the method name in the @covers line). I also checked the test actually covered it, and they do.

4.
./vendor/phpunit/phpunit/phpunit --filter=DrupalTest --coverage-html /tmp/report
before: Trying to @cover or @use not existing method "::requestStack"
after:

checked this is adding the correct default class.
checked each test, and it does call the method specified by the additional @covers
only the ::l one looked strange, cause it didn't match the oneline summary which said _l() (but I think that summary is wrong, it is called l() )

5.

+++ b/core/tests/Drupal/Tests/Core/DrupalTest.php
@@ -195,6 +224,8 @@ public function testEntityQuery() {
    * Tests the entityQueryAggregate() method.
+   *
+   * @covers ::entityQueryAggregate

we might be able to say in this issue to take out these one line summaries.. *but* the working group https://www.drupal.org/governance/technical-working-group has not actually set the standard for our phpunit docs #2057905: [policy, no patch] Discuss the standards for phpunit based tests, so might as well leave these in.

6.
6a.

+++ b/core/tests/Drupal/Tests/Core/Entity/BaseFieldDefinitionTest.php
@@ -46,7 +46,7 @@ protected function setUp() {
-        'some_setting' => 'value 1'
+        'some_setting' => 'value 1',

is out of scope.

6b.

+++ b/core/tests/Drupal/Tests/Core/Entity/BaseFieldDefinitionTest.php
@@ -305,7 +307,7 @@ public function testNullDefaultValueCallback() {
-  public static function mockDefaultValueCallback($entity, $definition) {
+  public static function mockDefaultValueCallback(EntityInterface $entity, FieldDefinitionInterface $definition) {

this might be out of scope also.. is it needed to get the --strict to run?

6c.
./vendor/phpunit/phpunit/phpunit --filter=BaseFieldDefinitionTest --coverage-html /tmp/report
before:
(this time it does run)

................

Time: 1.07 minutes, Memory: 130.75Mb

OK (16 tests, 27 assertions)

Generating code coverage report in HTML format ...

after:

................

Time: 1.03 minutes, Memory: 134.75Mb

OK (16 tests, 29 assertions)

Generating code coverage report in HTML format ...

so then I tried running with --strict:
./vendor/phpunit/phpunit/phpunit --filter=BaseFieldDefinitionTest --strict --coverage-html /tmp/report2
before:

RRRRRRRRRRRRRRRR

Time: 1.18 minutes, Memory: 130.75Mb

OK, but incomplete, skipped, or risky tests!
Tests: 16, Assertions: 27, Risky: 16.

Generating code coverage report in HTML format ...

after:

RRRRRRRRRRRRRRRR

Time: 1.62 minutes, Memory: 134.75Mb

OK, but incomplete, skipped, or risky tests!
Tests: 16, Assertions: 29, Risky: 16.

6d. same number of risky tests.
hm. but only 2 more assertions. odd because patch adds asserts (assertNotNull) to 3 tests that did not have any asserts. 3. does something similar. and the results of the code coverage (even with --strict) before and after are the same... so maybe these added assertNotNull() are out of scope also?

7.
fixes @covers
but also adds assertNotNull() to add assert to tests.

./vendor/phpunit/phpunit/phpunit --filter=SqlContentEntityStorageSchemaTest --coverage-html /tmp/report
before: Trying to @cover or @use not existing method "\Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema::getSchema".
after:

8.
./vendor/phpunit/phpunit/phpunit --filter=ContextHandlerTest --coverage-html /tmp/report
before: Trying to @cover or @use not existing method "\Drupal\Core\Plugin\Context\ContextHandler::__construct".
after: finishes

(yeah, and I think setUp() should not @cover anything?)

9.
./vendor/phpunit/phpunit/phpunit --filter=DefaultPluginManagerTest --coverage-html /tmp/report
before: .......Trying to @cover or @use not existing method "\Drupal\Core\Plugin\DefaultPluginManager::enforcePluginInterface".
after: finishes

ok. removes a wrong @covers.
but also adds asserts for two tests that did not have asserts.

10.
./vendor/phpunit/phpunit/phpunit --filter=AccessAwareRouterTest --coverage-html /tmp/report
before ..Trying to @cover or @use not existing method "\Drupal\Core\Routing\Router::__call".
after finishes

this makes sense here, the change in the default covers class

11.
./vendor/phpunit/phpunit/phpunit --filter=ReverseProxyMiddlewareTest --coverage-html /tmp/report
before: OK (4 tests, 6 assertions)
after: OK (3 tests, 6 assertions)

yeah, the naming of the data provider method should not start with test.
and that is why there is 1 less test (and the same number of assertions, cause there are no assertions in a provider)
(but I dont know if --strict caught that for you...)

trying
./vendor/phpunit/phpunit/phpunit --filter=ReverseProxyMiddlewareTest --strict --coverage-html /tmp/report
before:

...R

Time: 1.35 minutes, Memory: 130.50Mb

OK, but incomplete, skipped, or risky tests!
Tests: 4, Assertions: 6, Risky: 1.

after:

...

Time: 1.4 minutes, Memory: 134.25Mb

OK (3 tests, 6 assertions)

yeah, this time I can see a change in the risky test numbers using --strict, so this looks in scope.

mile23’s picture

Status: Needs work » Needs review
StatusFileSize
new20.1 KB
new985 bytes

Thanks for the review.

Re: item 3 and 6d

3: phpstorm complains that a void menthod is having it's result used.
but asserting something seems better than relying on not an exception.

6d: hm. but only 2 more assertions. odd because patch adds asserts (assertNotNull) to 3 tests that did not have any asserts. 3. does something similar. and the results of the code coverage (even with --strict) before and after are the same... so maybe these added assertNotNull() are out of scope also?

So a test is risky if it doesn't have an assertion (and doesn't have an expected exception, and doesn't have an ->expects() mock method). It's also just better form to explicitly state your assertion, so the next person can see what you were actually testing. There are probably better ways to write tests of methods that return NULL, but I erred on the side of preserving the test the way it is and just adding a reasonable assertion.

As far as assertNotNull(), I added that so they're not risky. A real test would be to assert that the returned object was of a specific class or interface, but since these are existing tests I just want them to not be risky.

6a and b: Both are coding standards issues.

8:

(yeah, and I think setUp() should not @cover anything?)

True. And also, we need to move away from the $this->classUnderTestWhichIsNotIsolatedFromOtherTests->methodToTest()-in-setup() pattern inherited from SimpleTest, but that's ouside this issue. :-)

11:

[regarding a provider method starting with test*] (but I dont know if --strict caught that for you...)

You got it. Adding --strict to the testbot setup is highly contentious for some reason that baffles me, so instead of having it catch errors like this one and burp back a testbot fail, I'm writing this catch-up issue and you're reviewing it.

So here's another patch, removing the coding standards changes in 6a and 6b.

mile23’s picture

StatusFileSize
new20.25 KB
new1.54 KB

And what the heck. Here's one with assertInstanceOf() instead of assertNotNull(), in circumstances like this:

    $this->assertInstanceOf('Drupal\Core\Field\BaseFieldDefinition',
      $definition->setDefaultValueCallback(NULL)
    );

...in order to be a more specific test.

yesct’s picture

I dont see how the changes 6c
effect the output of
./vendor/phpunit/phpunit/phpunit --filter=BaseFieldDefinitionTest --strict --coverage-html /tmp/report2

it passes strict before (I think the OK means it passes), and the results of the number of risky tests are the same: 16.
Is there something else we need to do here to make them not risky?
Or are the asserts part of moving them toward not being risky?

mile23’s picture

Well you'll recall the goal was to make a coverage report. :-)

Combining --strict and --coverage-[anything] apparently turns on a super-strict mode for coverage, where any method call not explicitly @covered or @used gets flagged as risky.

In truth, it *is* risky, because it's not in isolation, so we can't really unit test it. If we're not really unit testing that's fine, but in this case we're trying to.

So if you apply the patch and then do ./vendor/bin/phpunit --strict --coverage-text -v (note the -v), you'll see a bunch of output about which methods are called without being @covered or @used. They're the ones used by BaseFieldDefinitionTest::setUp() to create the fixture and set the \Drupal container.

A superficial fix would be to mark all those methods as @used in setUp(). A better and less superficial fix would be to refactor setUp() as a fixture-generating method called by test methods, with @uses annotated on the test method. The best solution of all would be to remove \Drupal from Drupal and do proper dependency injection everywhere. :-)

And of course, the most superficial fix of all is to never use --strict with --coverage-text.

Basically, Drupal unit tests don't come with a lot of discipline. So which is the best solution?

mile23’s picture

StatusFileSize
new20.3 KB
new2.15 KB

Here's another one.

Fixed the _l() docs. I think it's probably best policy to leave documentation where possible, and if someone wants to tackle removing the summary lines they can start another issue about it.

Made the BaseFieldDefinitionTest assertions more specific.

There's also a larger issue with DefaultPluginManagerTest because it's testing ::createInstance(), which it inherits but doesn't implement. That test should really be testing Drupal\Component\Plugin\PluginManagerBase. But again, that's out of the scope of this issue.

mile23’s picture

Added an issue about the PluginManagerBase issue mentioned above: #2371531: Expand unit testing for Drupal\Component\Plugin\PluginManagerBase

I think we should keep the tests from DefaultPluginManagerTest (patched, of course...), as well as the new tests from the new issue.

mile23’s picture

StatusFileSize
new10.33 KB

Re-scoping this issue so people can read it, understand it, and RTBC it. :-)

This patch contains only changes from the previous patch which deal with @covers exclusively. So files which had code changes are excluded.

This means it's easy to just see that they're @covers issues. I will file other issues with the other classes previously touched on here.

mile23’s picture

Title: Your semi-regular unit test QA audit (Wrong @covers, doesn't pass --strict) » Wrong @covers throughout unit tests
Issue summary: View changes
mile23’s picture

Status: Needs review » Postponed
Related issues: +#2358657: Wrong @covers definitions in Drupal project

OK.... I'm marking this issue as postponed to concentrate efforts on this overlapping issue: #2358657: Wrong @covers definitions in Drupal project. Will re-open after that drops.

geerlingguy’s picture

To jump in on #19; I'd love to see #2358657: Wrong @covers definitions in Drupal project go in soon so my automated tests/coverage report graphs can have joy and happiness again—they have been empty for some time now :(

geerlingguy’s picture

Status: Postponed » Needs review

#2358657: Wrong @covers definitions in Drupal project is fixed, reopening this issue.

Also, is #2287385: Fix PHPUnit coverage tests related to this issue?

mile23’s picture

I don't think that one is related.

But you might draw your attention to another issue while I reroll this one: #2328919: Remove () from a bunch of @covers definitions in PHPUnit

mile23’s picture

Title: Wrong @covers throughout unit tests » Add @covers annotation, fix some --strict for PHPUnit
Issue summary: View changes
StatusFileSize
new14.68 KB

OK....

Undo the scope change from #17, because the superficial @covers changes happened in #2358657: Wrong @covers definitions in Drupal project.

This patch is a re-roll of #15.

It adds assertions in many places where there are none.

It adds @covers annotation and very minor docblock corrections to DrupalTest.php, which tests \Drupal.

And it fixes a data provider mis-name in ReverseProxyMiddlewareTest.php. The data provider method started with 'test*', so PHPUnit tried to run it as a test.

yesct’s picture

  1. +++ b/core/tests/Drupal/Tests/Core/DrupalTest.php
    @@ -228,6 +262,8 @@ public function testModuleHandler() {
        * Tests the typedData() method.
    +   *
    +   * @covers ::typedDataManager
    
    @@ -277,8 +320,9 @@ public function testLinkGenerator() {
    -   * Tests the _l() method.
    +   * Tests the l() method.
        *
    +   * @covers ::l
    

    similar to the rationale for fixing the _l() comment, we should fix this since there is no typedData() method.

  2. +++ b/core/tests/Drupal/Tests/Core/Entity/BaseFieldDefinitionTest.php
    +++ b/core/tests/Drupal/Tests/Core/Entity/BaseFieldDefinitionTest.php
    @@ -295,7 +295,8 @@ public function testCustomStorage() {
    
    +++ b/core/tests/Drupal/Tests/Core/Plugin/DefaultPluginManagerTest.php
    @@ -276,7 +276,7 @@ public function testGetDefinitionsWithoutRequiredInterface() {
    -    $plugin_manager->getDefinitions();
    +    $this->assertInternalType('array', $plugin_manager->getDefinitions());
    

    I looked at
    https://phpunit.de/manual/current/en/appendixes.assertions.html#appendix...

    to check if it fails if it gets NULL, but it didn't say there.

yesct’s picture

+++ b/core/tests/Drupal/Tests/Core/Entity/Sql/SqlContentEntityStorageSchemaTest.php
@@ -898,7 +904,9 @@ public function testDedicatedTableSchema() {
+      $this->storageSchema->onFieldStorageDefinitionCreate($field_storage)

should we add a @covers for onFieldStorageDefinitionCreate ?

mile23’s picture

StatusFileSize
new15.44 KB
new1.56 KB

#24.1: Good catch.

#24.2: NULL is a type. :-) But also, yah, assertInternalType will barf if it's not an array.

#25: Since I've decided these tests are asserting the result of onFieldStorageDefinitionCreate(), then yes. If I'm wrong, then no. But I'm pretty sure I'm right.

However, here's the entire source code for onFieldStorageDefinitionCreate():

public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $storage_definition) {
  $this->performFieldSchemaOperation('create', $storage_definition);
}

performFieldSchemaOperation() doesn't call the other methods annotated as @covered in those two tests, and all the other dependencies are fully mocked. So therefore: Remove the other @covers annotations. :-) This will probably cause a need for a reroll on #2328919: Remove () from a bunch of @covers definitions in PHPUnit.

Really, there should be a unit test for performFieldSchemaOperation(), because there isn't. That's out of scope here, however.

yesct’s picture

Issue summary: View changes
Status: Needs review » Reviewed & tested by the community

ok. updating the issue summary to reflect what I understand about this issue.

yesct’s picture

Issue summary: View changes
alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed c1b037d and pushed to 8.0.x. Thanks!

  • alexpott committed c1b037d on 8.0.x
    Issue #2364555 by Mile23: Add @covers annotation, fix some --strict for...
mile23’s picture

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.