Asked  7 Months ago    Answers:  5   Viewed   40 times

I found the discussion on Do you test private method informative.

I have decided, that in some classes, I want to have protected methods, but test them. Some of these methods are static and short. Because most of the public methods make use of them, I will probably be able to safely remove the tests later. But for starting with a TDD approach and avoid debugging, I really want to test them.

I thought of the following:

  • Method Object as adviced in an answer seems to be overkill for this.
  • Start with public methods and when code coverage is given by higher level tests, turn them protected and remove the tests.
  • Inherit a class with a testable interface making protected methods public

Which is best practice? Is there anything else?

It seems, that JUnit automatically changes protected methods to be public, but I did not have a deeper look at it. PHP does not allow this via reflection.

 Answers

86

If you're using PHP5 (>= 5.3.2) with PHPUnit, you can test your private and protected methods by using reflection to set them to be public prior to running your tests:

protected static function getMethod($name) {
  $class = new ReflectionClass('MyClass');
  $method = $class->getMethod($name);
  $method->setAccessible(true);
  return $method;
}

public function testFoo() {
  $foo = self::getMethod('foo');
  $obj = new MyClass();
  $foo->invokeArgs($obj, array(...));
  ...
}
Wednesday, March 31, 2021
 
Maury
answered 7 Months ago
100

For testing properties, I'd make the same arguments I make then talking about testing private methods.

You usually don't want to do this.

It's about testing observable behavior.

If you rename all your properties or decide to store them into an array you should not need to adapt your tests at all. You want your tests to tell you that everything still works! When you need to change the tests to make sure everything still works you lose all the benefits as you also could make an error changing the tests.

So, all in all, you lose the value of you test suite!


Just testing the get/set combinations would be ok enough but usually not every setter should have a getter and just creating them for testing is not a nice thing ether.

Usually, you set some stuff and then tell the method to DO (behavior) something. Testing for that (that the class does what is should do) is the best option for testing and should make testing the properties superfluous.


If you really want to do that there is the setAccessible functionality in PHP reflections API but I can't make up an example where I find this desirable

Finding unused properties to catch bugs / issues like this one:

The PHP Mess Detector As a UnusedPrivateField Rule

class Something
{
    private static $FOO = 2; // Unused
    private $i = 5; // Unused
    private $j = 6;
    public function addOne()
    {
        return $this->j++;
    }
}

This will generate two warnings for you because the variables are never accessed

Wednesday, March 31, 2021
 
Drazisil
answered 7 Months ago
81

I'll start of by linking to the manual and then going into what I've seen and heard in the field.

Organizing phpunit test suites

Module / Test folder organization in the file system

My recommended approach is combining the file system with an xml config.

tests/
  unit/
 | - module1
 | - module2
 - integration/
 - functional/

with a phpunit.xml with a simple:

<testsuites>
  <testsuite name="My whole project">
    <directory>tests</directory>
  </testsuite>
</testsuites>

you can split the testsuites if you want to but thats a project to project choice.

Running phpunit will then execute ALL tests and running phpunit tests/unit/module1 will run all tests of module1.

Organization of the "unit" folder

The most common approach here is to mirror your source/ directory structure in your tests/unit/ folder structure.

You have one TestClass per ProductionClass anyways so it's a good approach in my book.

In file organization

  • One class per file.

It's not going to work anyways if you have more than one test class in one file so avoid that pitfall.

  • Don't have a test namespace

It just makes writing the test more verbose as you need an additional use statement so I'd say the testClass should go in the same namespace as the production class but that is nothing PHPUnit forces you to do. I've just found it to be easier with no drawbacks.

Executing only a few tests

For example phpunit --filter Factory executes all FactoryTests while phpunit tests/unit/logger/ executes everything logging related.

You can use @group tags for something like issue numbers, stories or something but for "modules" I'd use the folder layout.

Multiple xml files

It can be useful to create multiple xml files if you want to have:

  • one without code coverage
  • one just for the unit tests (but not for the functional or integration or long running tests)
  • other common "filter" cases
  • PHPBB3 for example does that for their phpunit.xmls

Code coverage for your tests

As it is related to starting a new project with tests:

  • My suggestion is to use @covers tags like described in my blog (Only for unit tests, always cover all non public functions, always use covers tags.
  • Don't generate coverage for your integration tests. It gives you a false sense of security.
  • Always use whitelisting to include all of your production code so the numbers don't lie to you!

Autoloading and bootstrapping your tests

You don't need any sort of auto loading for your tests. PHPUnit will take care of that.

Use the <phpunit bootstrap="file"> attribute to specify your test bootstrap. tests/bootstrap.php is a nice place to put it. There you can set up your applications autoloader and so on (or call your applications bootstrap for that matter).

Summary

  • Use the xml configuration for pretty much everything
  • Seperate unit and integration tests
  • Your unit test folders should mirror your applications folder structure
  • To only execute specif tests use phpunit --filter or phpunit tests/unit/module1
  • Use the strict mode from the get go and never turn it off.

Sample projects to look at

  • Sebastian Bergmanns "Bank Account" example project
  • phpBB3 Even so they have to fight some with their legacy ;)
  • Symfony2
  • Doctrine2
Wednesday, March 31, 2021
 
RompelStompel
answered 7 Months ago
49

You could just store up the failures for the end, say with a

$passing = true;
if (! false) { $passing = false; }
if (! true) { $passing = false; }
$this->assertTrue($passing);

but I highly discourage this form of testing. I have written tests like this, and they exponentially get out of hand, and worse, you start to get weird failures for hard-to-find reasons.

Additionally, smarter people than me agree, tests should not have any conditionals (if/else, try/catch), because each conditional adds significant complexity to the test. If a conditional is needed, perhaps both the test and the SUT, or System Under Test, should be looked at very carefully, for ways to make it simpler.

A much better way would be to change it to be two tests, and if they share a significant portion of the setup, then move those two tests into a new test class, with the shared setup performed in the Setup() method.

Saturday, May 29, 2021
 
RenegadeAndy
answered 5 Months ago
81

The issue is that PHPUnit will print a header to the screen and at that point you can't add more headers.

The work around is to run the test in an isolated process. Here is an example

<?php

class FooTest extends PHPUnit_Framework_TestCase
{
    /**
     * @runInSeparateProcess
     */
    public function testBar()
    {
        header('Location : http://foo.com');
    }
}

This will result in:

$ phpunit FooTest.php
PHPUnit 3.6.10 by Sebastian Bergmann.

.

Time: 1 second, Memory: 9.00Mb

OK (1 test, 0 assertions)

The key is the @runInSeparateProcess annotation.

If you are using PHPUnit ~4.1 or something and get the error:

PHP Fatal error:  Uncaught Error: Class 'PHPUnit_Util_Configuration' not found in -:378
Stack trace:
#0 {main}
  thrown in - on line 378

Fatal error: Uncaught Error: Class 'PHPUnit_Util_Configuration' not found in - on line 378

Error: Class 'PHPUnit_Util_Configuration' not found in - on line 378

Call Stack:
    0.0013     582512   1. {main}() -:0

Try add this to your bootstrap file to fix it:

<?php
if (!defined('PHPUNIT_COMPOSER_INSTALL')) {
    define('PHPUNIT_COMPOSER_INSTALL', __DIR__ . '/path/to/composer/vendors/dir/autoload.php');
}
Monday, June 21, 2021
 
koenHuybrechts
answered 5 Months ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :
 
Share