4

Does this make sense:

As we generally only want to test the API (of a class) and not the implementation details we generally do not need/want to test protected methods. As we do not want code coverage to drop just because we do not test implementation details we should use the @codeCoverageIgnore annotation for those protected methods.

Without @codeCoverageIgnore With @codeCoverageIgnore

1
  • I'm curious about best practices here. Where I've been working we've just had no requirement for 100% coverage because of the things you mentioned. We instead had rules in CI to fail builds if coverage dropped more than X% Commented Mar 21, 2016 at 21:54

2 Answers 2

6

Test your private and protected methods through the public API.

The rule "don't test your privates" doesn't mean you shouldn't test the behaviour provided by private methods. It means you should test that behaviour via public methods. If you do this, you'll get the flexibility of changing the implementation later (i.e. create different private methods, or inline them).

Obviously you'll be writing multiple test cases for a single method you're testing. Make sure you name those methods to clearly state what you expect. For example:

  • test_it_reverses_the_name()
  • test_it_lowercases_characters_in_the_reversed_name()
  • test_it_throws_an_exception_if_name_is_missing()

Notice that if you're test driving a public method you don't often start by creating private methods. You rather extract them as a refactoring step. Later you might also decide to inline those methods without the need of changing tests. That's because you were only testing the public behaviour.

There's no need to use @codeCoverageIgnore or @covers. You'll be lying to yourself.

Example

Foo.php:

<?php class Foo { private $name; public function __construct($name) { $this->name = $name; } public function getReversedName() { $this->foo(); return strrev($this->name); } protected function foo() { $foo = true; } } 

FooTest.php:

<?php class FooTest extends \PHPUnit_Framework_TestCase { public function test_it_reverses_the_name() { $foo = new Foo('test'); $this->assertSame('tset', $foo->getReversedName()); } } 

phpunit.xml.dist:

<?xml version="1.0" encoding="UTF-8"?> <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd" backupGlobals="false" colors="true" bootstrap="vendor/autoload.php" > <php> <ini name="error_reporting" value="-1" /> </php> <testsuites> <testsuite name="Project Test Suite"> <directory>tests</directory> </testsuite> </testsuites> <logging> <log type="coverage-html" target="build/coverage"/> </logging> </phpunit> 

coverage

Sign up to request clarification or add additional context in comments.

3 Comments

I agree but without using either @codeCoverageIgnore or @covers I experienced that the Code Coverage will drop and the protected/private methods are marked as untested (red). Please have a look at the screenshots (especially the foo() method) in my original question. Do you also experience this and how do you deal with it?
No, that's not my experience. In my experience if your test calls a method that calls a private method, both will be covered. Make sure code in your private is actually reached in the test.
Also, I noticed your private methods only assign a value to a local variable. There's something wrong with the way coverage is calculated and the closing bracket is sometimes not included. Since you only have one line method maybe something went wrong there. Try returning from those methods just for verification if anything changes in coverage.
0

If One test cover more than one class method you can use the covers annotation as described in the doc here. As example:

/** * @covers Foo::foo * @covers Foo::bar */ public function testReversed() { $this->assertEquals(0, $this->foo->getReversedName()); } 

Hope this help

1 Comment

Thanks Matteo, you already suggested this in stackoverflow.com/a/31587557/999596 but I think @SamHolder has a good point (there) in his answer that your tests then maybe know too much about the implementation and you may frequently have to change these @covers annotations when you only change the implementation but not the API itself. What do you think?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.