Manual:PHP unit testing/Writing unit tests/ja

This page is a translated version of the page Manual:PHP unit testing/Writing unit tests and the translation is 15% complete.

PHPUnit マニュアルは、単体テストを理解および開発するための優れた手順を提供します。 テストの記述および整理の節に特に注意してください。

Developing best practices

MediaWiki での単体テストに不慣れな開発者は、出発点として SampleTest.php を使用する必要があります。これには、単体テストの作成方法を学ぶ過程を容易にする役立つコメントが含まれています。

もう 1 つのリソースは、OSCON 2010 で Sebastian Bergmann が行った PHPUnit の成功事例トークのスライドです。

Developers should avoid inventing new conventions or borrowing conventions from other frameworks; using the already well-established PHPUnit conventions will serve to make MediaWiki's tests both useful and usable.

Unit tests should follow A Set of Unit Testing Rules by Michael Feathers.

テスト可能なコードを書く

テスト可能なコードを書くよう心がけてください。

MediaWiki はテスト可能であることを目的として書かれていません。 あらゆる場所でグローバル変数を使用し、多くの場所で静的メソッドを使用します。 これは我々が受け入れなければならない遺産ですが、これらのことを新しいコードに導入しないようにし、今後変更するよう心がけてください

Miško Hevery のリソース Guide to Testability が役立つかもしれません。 (Miško Hevery は Google のアジャイル コーチ [の 1 人?] です。)

テストの規約

ファイル名は Test.php で終わる必要があります。 出発点として SampleTest.php を使用します。

setUp() と tearDown()

  • Must be protected functions.
  • tearDown() should be in the opposite order of setUp().
  • setUp() starts with calling its parent.
  • tearDown() ends with calling its parent.

Assertion functions

  • Must be public functions.
  • The name of the function should be in lowerCamelCase and begin with the word test; e.g., function testFooBar.
  • Whenever possible, refer to the most important method being tested; e.g., Html::expandAttributes is tested in HtmlTest::testExpandAttributes.

Data providers

  • Must be public functions.
  • Should be static functions. (See T332865.)
  • The name of the data provider should be in lowerCamelCase and begin with the word provide; e.g., provideHtml5InputTypes.
  • Should not instantiate MediaWiki Services, since data providers are called before setUpBeforeClass. For example, instead of Title::newFromText, use new TitleValue to avoid creating a TitleParser.

Telling a story

Test output should tell a story. The --testdox output format is a good way to view this story: a test suite execution is displayed as a set of statements about the test classes, along with whether or not they have passed. The statements (unless customized) are the test method names with corrected capitalization and spacing.

The @testdox annotation can be used to customize the message that is displayed. It is not currently used in the MediaWiki codebase.

See "Other Uses for Tests" for more information.

Number of assertions

Only one assertion per test unless there is a good reason (expensive tests may need to be grouped).

Failing

Usually tests code shouldn't do die("Error"), but use the phpunit fail method:

$this->fail( 'A useful error message' )

This would show up as a failure in the testing summary rather than bringing the whole test suite down.

Specific to MediaWiki

Grouping tests

PHPUnit allows tests to be put into arbitrary groups. Groups of tests can be selected for execution or excluded from execution when the test suite is run (see the @group annotation, The Command-Line Test Runner and XML Configuration File documentation in the PHPUnit manual for additional details.)

To add a test (or class) to a group, use the @group annotation in the docblock preceding the code. 例:

/**
 * @group Broken
 * @group Internationalization
 */
class HttpTest extends MediaWikiTestCase {
    ...

Several functional groups are currently used in MediaWiki unit tests:

  • APITests that exercise the MediaWiki API.
  • BrokenPut broken tests into group Broken. Tests in this group will not be run (as is configured in tests/phpunit/suite.xml).
  • DatabaseTests that require database connectivity should be put into group Database.
This causes temporary tables to be overlayed over the real wiki database, so test cases can perform database operations without changing the actual wiki.
  • DestructiveTests that alter or destroy data should be put into group Destructive.
  • SearchTests that use MediaWiki's built-in search put into group Search.
  • SeleniumFrameworkTests that require SeleniumFramework to be installed should be put in group SeleniumFramework.
  • StubPut test stubs into group Stub. Tests in this group will not be run (as is configured in tests/phpunit/suite.xml).
  • SqliteTests that use SQLite should be put into group Sqlite.
  • StandaloneVery slow tests that should not be run in the gate, to help keep tests fast for other gated extensions.
  • UploadTests that upload files should be put into group Upload.
  • UtilityCurrently unused by any test. Tests in this group will be not be run (as is configured in tests/phpunit/suite.xml).

In addition, tests may also be grouped based on development team:

  • Fundraising
  • EditorEngagement
  • Internationalization
  • etc.

To test only a particular group, use the --group flag from the command line:

composer phpunit:entrypoint -- --group Search

or if you use the Makefile in core/tests/phpunit:

make FLAGS="--group Search" target

where target can be phpunit, safe, etc.

カバー率

The PHPUnit documentation has a chapter about coverage. There is a coverage report for MediaWiki core generated twice a day. As the forceCoversAnnotation option should be in effect the test should be marked with @covers annotations to express which parts of the code the test actually checks (as opposed to code that is just run but whose results are never tested for with assertions).

Note that @covers requires fully-qualified class names (unlike Doxygen annotations such as @param).

Class

You will want to extend one of the MediaWiki test classes.

class HttpTest extends MediaWikiTestCase {
    ...

The following are some common MediaWiki test classes to extend. Bullets at a lower level extend their parent.

  • TestCasePHPUnit's test class
    • MediaWikiUnitTestCaseFor unit tests of methods with no dependencies, or methods whose dependencies are completely mocked. These should go in their own subfolder called /unit/, so that phpunit:unit works correctly.
      • HookRunnerTestBaseTests that all arguments passed into a HookRunner class are passed along to a HookContainer.
    • MediaWikiIntegrationTestCaseAssists with testing classes that access global variables, methods, services, or a storage backend. Prevents sending actual emails. These should go in their own subfolder called /integration/, so that phpunit:integration works correctly. Can safely test the SQL database if you add @group Database to your tests. Changes to database data are reset at the beginning of each test method.
      • MediaWikiLangTestCaseDoes some language setup such as $this->setUserLang( 'en' ); and $this->setContentLang( 'en' ); and does $services->getMessageCache()->disable()
        • ApiTestCase –- Has some extra methods for testing the Action API , such as doApiRequest(), doApiRequestWithToken(), buildFauxRequest(), etc.
      • MaintenanceBaseTestCaseFor testing MediaWiki maintenance classes.
      • SpecialPageTestBaseFor testing MediaWiki special pages.

データベース

When testing database-dependent code, you should put your test case in the Database group (see above). That tells MediaWikiTestCase to setup a DB_PRIMARY database connection for you to use in $this->db. Normally, this uses a separate temporary database, with certain limited data prefilled by addCoreDBData, including a 'UTSysop' user and a 'UTPage' title. (The temporary database is created with CREATE TEMPORARY TABLE, and the tables are prefixed with unittest_.) You can force PHPUnit to not use temporary tables (for example, if you want to step debug and look in the database using a database viewer) by setting the .env variable PHPUNIT_USE_NORMAL_TABLES=1. A test case can add additional data to the database by overriding addDBData (which by default does nothing).

Please note that only the table schema is copied over from your actual database, not the existing data in that table.

You can directly test the current contents of the database with assertSelect().

$this->assertSelect(
	'test', // Table
	[ 'first_name', 'last_name', 'street' ], // Fields to select
	[], // Conditions
	[ [ 'Jane', 'Doe', 'Broadway' ] ] // Expected values
);

Here are some examples from extensions that you can look at for reference:

Tests that are not in the Database group still run against a temporary cloned database (even if they ignore $this->db and instead use e.g. wfGetDB() directly); however, this database is only set up once for the whole test run, and not reset between test runs. Tests should avoid relying on this safety feature if possible.

メンテナンス スクリプト

Test cases for maintenance scripts should inherit from MediaWiki\Tests\Maintenance\MaintenanceBaseTestCase, to handle the different output channels used by maintenance scripts.

The base test case setUp() method will instantiate your Maintenance object for you, if you specify the class to construct by providing the mandatory getMaintenanceClass() in your subclass:

    public function getMaintenanceClass() {
        return PurgeScoreCache::class;
    }

In the unlikely event that you want to do something special to instantiate the class under test, you can override the createMaintenance() method, but hopefully this isn't needed.

By default, the maintenance script output will be suppressed and ignored. If you wish to test output (this is a good idea), use code like:

use MediaWiki\Tests\Maintenance\MaintenanceBaseTestCase;

class PurgeScoreCacheTest extends MaintenanceBaseTestCase {
    public function testNotAThing() {
        $this->maintenance->loadWithArgv( [ '--model', 'not_a_thing' ] );
        $this->maintenance->execute();

        $this->expectOutputRegex( '/skipping \'not_a_thing\' model/' );
    }
}