Eclipse integration

Developing Unit Tests

This is a very terse documentation of the PHPUnit features we usually use in our unit tests. It is meant to document PKP-specific usage of PHPUnit and as a cheat sheet for the most important PHPUnit functionality. It does not replace the PHPUnit manual. Please refer to the manual when you are using PHPUnit for the first time.

Nomenclature

Unit test classes are called after the class they are testing, e.g. the test to test the "Config" class would be "ConfigTest".

PHPUnit works well with our usual PKP file name nomenclature. Files are named after the test class name with a ".inc.php" postfix, e.g. the ConfigTest class would be found in the file "ConfigTest.inc.php".

We'll use the same folder structure inside the tests directory that we use for the classes we test.

Tests for PKP library classes will be in "lib/pkp/tests". Tests for PKP application classes will be in "/tests".

What should be tested

Generally you want to test only public interface and public object states. Also, test only methods with logic, including constructors.

Which base class to extend

Unit tests that require database access extend "DatabaseTestCase".

All other test cases extend "PKPTestCase".

What should be mocked

All tested code external dependencies. Doubles (or mocks, a type of double object) are meant to isolate the tested code, so we can unit test it without executing the other system parts.

You can also mock your class under test, to inject dependencies, for example. But be careful to not mock internal functionalities. That way you are not completely testing the class. Also, internal methods are implementation details and should be free to be changed. Mocking them would couple too much test and class under test.

How should template methods of abstract classes be tested

Test template methods of abstract classes mocking only the abstract methods that the template method needs. You should not test the public interface of an abstract class using a subclass. The subclass might have its own test, but it should only test details that are implemented there.

What to refactor to make it possible to test

Add getters and setters if you need to inject external dependencies. Eg.: ScheduledTask uses Mail class, so it has a getMail() method that can be mocked to return the mail double created to avoid that external class dependency.

Consider that tests are also users of the application. So you might want to expose some interfaces to make it easier to test the class. This should be taken with care, you don't want to expose something that's clearly meant to be private to make the class easier to test.

How to write maintainable tests

Avoid much logic into the tests methods. Tests should be simple.

Each test should test only one thing, ideally. Sometimes is hard, but tests that needs lots of mock objects and logic are almost testing more than they should. That makes harder to read and understand the code.

Avoid tested code duplication in tests. A symptom might be the necessity of a lot of logic in your tests. Pay attention to one test template being used with a data provider to test a lot of cases. This could be a tested code logic duplication code smell.

When unit turns into integration

If your test depends on anything outside your tested class, mock it. If you don't do that, you are not unit testing, you are doing an integration test. When you allow external dependencies running their code, your test code is coupled with them, and if something changes there, it might brake your unit test. That makes tests harder to maintain.

Also, make sure you don't test how the external dependencies worked. You can test what parameters your testing class passed to the external dependencies, if it's needed. But you don't want to test if and how they were stored by the dependency entities.

Test Documentation

One of the functions of a test case is documenting the class specification in a very detailed and precise way. Please make sure that your source code contains enough method and inline documentation so that an outsider can quickly understand how the class' API is to be used. This is not necessary if the test method name speaks for itself.

Use a @see annotation in the class comment that points to the tested class.

How to test exceptions

A test method that is annotated with "@expectedException ExceptionClass" will fail if it does not throw the declared exception.

What if my test class calls a DAO?

We always try to test classes with as little dependencies as possible. We want our unit tests to concentrate on one class without relying on the functionality of other classes.

An example: We do not want to rely on a functioning database infra-structure when testing application objects that draw data from the DAO application layer.

Luckily PHPUnit provides us with a very elegant and lean solution to this dependency problem. See the following test code for an example:

class SubmissionTest extends PKPTestCase {
[...]
public function testGetUser() {
// Our system under test (SUT)
$submission = new Submission();
$submission->setUserId(5);
// Mock the UserDAO
$mockUserDAO =& $this->getMock('UserDAO', array('getUser'));
DAORegistry::registerDAO('UserDAO', $mockUserDAO);
// Set up the mock getUser() method
$result = new PKPUser();
$result->setId(5);
$mockUserDAO->expects($this->once()) // getUser should be called once
->method('getUser')
->with($this->equalTo(5)) // expected input
->will($this->returnValue($result)); // mock output
// The following assertion will pass if and only if the following
// conditions are met:
// * UserDAO->getUser() is called exactly once
// * UserDAO->getUser() is called with one parameter equal to 5
// * $submission->getUser() returns exactly the object we return from
// our mock object.
self::assertEquals($result, $submission->getUser());
}
[...]
}

What if my test class depends on calls to static class methods?

From a testing point of view, static method calls are evil. It is impossible to mock static method calls with the system under test being unaware of it. Static method calls are not so nice for other reasons as well: They make it difficult to "drop in" different implementations of a class at runtime. This restricts design flexibility and code maintainability. It is usually better to use the singleton pattern instead.

If you cannot avoid static method calls or if you have to rely on legacy classes then you can manually create a mock class that will be included rather than the standard PKP implementation:

create a file called "Mock<Classname>.inc.php" in the same directory as your unit test class.

in this file implement a mock version of <Classname>.

implement only the methods required either during PKP framework initialization or for your own test.

the test framework will automatically drop in the mock class instead of the original class

If you need the same mock class several times then you can implement it in the 'tests/mock' folder and drop a file with the same name into your current unit test directory that imports the central mock class from the 'tests/mock' folder.

Here is an example for a mock Locale class. The class is placed in the file MockLocale.inc.php in the '/lib/pkp/tests/mock' directory. It is being included from many different unit test directories. One example is '/lib/pkp/tests/classes/core'.

PHPUnit annotations

Declaring Coverage

The @covers annotation indicates to the test coverage analyser the class or method to be covered by a given unit test. The @covers annotation is mandatory for all tests. The function format is always preferable to the class format. You'll very rarely write unit tests that cover a whole class.

method format: @covers ClassName::methodName

class format: @covers ClassName

Documenting Test Dependency

The @depends defines a logical dependency of one test case from another. You use the depends annotation if you want to document such dependency or if you want to use results from one test case as input to another test case. Test execution order is only dependent on the order of the methods in the test class, not on the @depends annotation.

What if my test fails with an error?

You've got various possibilities to debug your test. Usually the test will show a backtrace when there was an error. If this is not the case then have a look in "tests/results/error.log". Errors that occur within the PKP library will be logged there.

Installing and Configuring the Build Environment

Phing

On the command line execute

pear channel-discover pear.phing.info
pear install phing/phing

The section about the build environment is not complete yet. Please ignore it for the time being!