Another test case decorator: SvnFixtures.
Svn subcommands:
createRepo($subversion_repository_path)
checkout($repo_path, $to_path)
add($path)
remove($path)
commit($path)
update($path)
lock($path)
unlock($path)
All the svn subcommand methods return the combined stdout/stderr output, if that's of interest.
I didn't religiously add every subcommand, just the ones I've needed so far. These help tests to read better but you can also drop a valid `svn ...` command string (minus the backticks) into this method:
executeSvnCommand($command) - $command will be suffixed with " --non-interactive"
Just one new assertion — but you'll also have all the FileSystemFixtureTools bits and pieces (see below).
assertLogPattern( $pattern, $working_copy_path, $args = array('-v'), $message = '%s')
Some debugging methods (dump to screen):
dumpStatus($path) - svn status -u
dumpInfo($path) - svn info
dumpLog($path, $args = array('-v', '--limit 10'))
createFixture($label)
Tests which interact with a subversion repository are orders of magnitude slower than anything else I've ever had to work with. Hence SvnFixtures provides a fixture caching mechanism. Once a subversion fixture — a repository, working copies and files — has been created, everything gets bagged up in a gzip file. On the next test run, the fixture can be unzipped quickly from cache rather than created slowly with svn commands.
What you gain from this depends on the details. Obviously some subversion commands do have to be made in tests and so they're never going to be lightning fast. However, if most of the subversion calls are concerned with fixture set up, and hence are cachable, you should see a dramatic improvement.
The basic idea is to extend SvnFixtures, adding a method for each fixture you need to create. For example, a simple fixture with a subversion repository and working copy:
<?php
class FooFixture extends SvnFixtures {
function getFixtureStore() {
return '/your/chosen/dir/'; // anything but the sandbox...
}
function repositoryPath() {
return $this->fixtureSandbox() . 'repository/';
}
function workingCopyPath() {
return $this->fixtureSandbox() . 'working-copy/';
}
function foo() {
$this->createRepo($this->repositoryPath());
$this->checkout(
$this->repositoryPath(),
$this->workingCopyPath());
}
}
There's an abstract method to implement: getFixtureStore(). This is the dir where fixture tarballs will be saved — set this wherever you like. Note that different test cases can all use the same dir without worrying about different fixtures with the same name overwriting each other. The program will manage that internally.
Note: don't put fixture cache files under version control. Fixtures may contain details specific to the machine on which they were built (eg a host name) and so may not work on another.
In the test case, you'll need to refer to repository & working copy paths. Since FooFixture is a test case decorator, just add a couple of accessor methods. These will be injected into the test case.
Subversion needs to interact with the filesystem and so the FileSystemFixtureTools are added automatically, behind the scenes. Always create fixtures in $this->fixtureSandbox(), as shown above. Internally this is set to the FileSystemFixtureTools::sandbox(). The entire sandbox will be tarred up when the fixture is saved.
Register your SvnFixtures subclass:
<?php
class MyTests extends AperiUnitTest {
function beforeInvoking() {
$this->addFixture('FooFixtures');
}
}
Now everything is wired up and you can start testing. To create a fixture, call createFixture() with the name of a fixture method. For example, for FooFixture::foo() pass the method name "foo".
<?php
class MyTests extends AperiUnitTest {
function beforeInvoking() {
...
}
function testSomething() {
$this->createFixture('foo');
...
}
The first time a test is run, FooFixture::foo() will be called and the fixture is built from scratch. On subsequent test runs, the fixture files are restored from cache and FooFixture::foo() is not called.
Some fixtures are subsets of other fixtures, containing common files. One fixture method can call another but make sure you always do it through createFixture() rather than calling the method directly. This ensures that the call is heard by the caching mechanism.
<?php
class FooFixtures extends SvnFixtures {
function otherFixture() {
...
}
function someFixture() {
// $this->otherFixture(); NO!
$this->createFixture('otherFixture');
...
...
}
Of course you can also stack up a bunch of separate fixture objects. Fixtures will hear setUp in the order in which they were registered and teardown in reverse order.
Note that, if you edit a fixture method, you must refresh the cache.
Use the "r" option to force a refresh. All fixtures directly involved in the specified test run will be rebuilt from scratch:
$ php test.php -r path/to/test-case.php
Note that fixture caching can also be turned off with a registry value. See aperiplus/config-sample.php. Caching is best deactivated if you don't know how caches can become stale, and hence when you need to refresh. Developers will want to use caching but, if you intend end-users to run a test suite in order to check if your app is correctly installed, turn caching off.
You should not declare class properties in fixture-creation methods because the method won't always be run. When the fixture is restored from cache the property won't be set. Locally scoped vars used to build the fixture are OK, and you can of course refer to class properties declared elsewhere.
Sqlite databases cannot be restored from a sandbox cache. The connection is lost at teardown when files are deleted.
By default, SvnFixtures assumes an AperiRegistry is being used. A config file would define values for host, user and sandbox dir eg:
<?php
$registry->set('host', trim(`hostname`));
$registry->set('user', trim(`whoami`));
$registry->set('sandbox-dir', '/mnt/sandbox/testing/');
etc..
However, requiring the use of AperiRegistry is a bit of an imposition. If you want to substitute your own preferred solution override the methods:
You can either subclass one of the above or implement the method(s) in the test case itself (a test case always overrides its decorators — see method injection).
SvnFixtures::shouldUseCaching() activates/deactivates the caching system. Return boolean.