Kohana v3.3.0

This commit is contained in:
Deon George
2013-04-22 14:09:50 +10:00
commit f96694b18f
1280 changed files with 145034 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
# UnitTest
Unit tests for Kohana

View File

@@ -0,0 +1,5 @@
## [UnitTest]()
- [Mock Objects](mockobjects)
- [Testing](testing)
- [Testing workflows](testing_workflows)
- [Troubleshooting](troubleshooting)

View File

@@ -0,0 +1,265 @@
# Mock objects
Sometimes when writing tests you need to test something that depends on an object being in a certain state.
Say for example you're testing a model - you want to make sure that the model is running the correct query, but you don't want it to run on a real database server. You can create a mock database connection which responds in the way the model expects, but doesn't actually connect to a physical database.
PHPUnit has a built in mock object creator which can generate mocks for classes (inc. abstract ones) on the fly.
It creates a class that extends the one you want to mock. You can also tell PHPUnit to override certain functions to return set values / assert that they're called in a specific way.
## Creating an instance of a mock class
You create mocks from within testcases using the getMock() function, which is defined in `PHPUnit_Framework_TestCase` like so:
getMock($originalClassName, $methods = array(), array $arguments = array(), $mockClassName = '', $callOriginalConstructor = TRUE, $callOriginalClone = TRUE, $callAutoload = TRUE)
`$originalClassName`
: The name of the class that you want to mock
`$methods`
: The methods of $originalClassName that you want to mock.
You need to tell PHPUnit in advance because PHP doesn't allow you to extend an object once it's been initialised.
`$arguments`
: An array of arguments to pass to the mock's constructor
`$mockClassName`
: Allows you to specify the name that will be given to the mock
`$callOriginalConstructor`
: Should the mock call its parent's constructor automatically?
`$callOriginalClone`
: Should the mock call its parent's clone method?
Most of the time you'll only need to use the first two parameters, i.e.:
$mock = $this->getMock('ORM');
`$mock` now contains a mock of ORM and can be handled as though it were a vanilla instance of `ORM`
$mock = $this->getMock('ORM', array('check'));
`$mock` now contains a mock of ORM, but this time we're also mocking the check() method.
## Mocking methods
Assuming we've created a mock object like so:
$mock = $this->getMock('ORM', array('check'));
We now need to tell PHPUnit how to mock the check function when its called.
### How many times should it be called?
You start off by telling PHPUnit how many times the method should be called by calling expects() on the mock object:
$mock->expects($matcher);
`expects()` takes one argument, an invoker matcher which you can create using factory methods defined in `PHPUnit_Framework_TestCase`:
#### Possible invoker matchers:
`$this->any()`
: Returns a matcher that allows the method to be called any number of times
`$this->never()`
: Returns a matcher that asserts that the method is never called
`$this->once()`
: Returns a matcher that asserts that the method is only called once
`$this->atLeastOnce()`
: Returns a matcher that asserts that the method is called at least once
`$this->exactly($count)`
: Returns a matcher that asserts that the method is called at least `$count` times
`$this->at($index)`
: Returns a matcher that matches when the method it is evaluated for is invoked at the given $index.
In our example we want `check()` to be called once on our mock object, so if we update it accordingly:
$mock = $this->getMock('ORM', array('check'));
$mock->expects($this->once());
### What is the method we're mocking?
Although we told PHPUnit what methods we want to mock, we haven't actually told it what method these rules we're specifiying apply to.
You do this by calling `method()` on the returned from `expects()`:
$mock->expects($matcher)
->method($methodName);
As you can probably guess, `method()` takes one parameter, the name of the method you're mocking.
There's nothing very fancy about this function.
$mock = $this->GetMock('ORM', array('check'));
$mock->expects($this->once())
->method('check');
### What parameters should our mock method expect?
There are two ways to do this, either
* Tell the method to accept any parameters
* Tell the method to accept a specific set of parameters
The former can be achieved by calling `withAnyParameters()` on the object returned from `method()`
$mock->expects($matcher)
->method($methodName)
->withAnyParameters();
To only allow specific parameters you can use the `with()` method which accepts any number of parameters.
The order in which you define the parameters is the order that it expects them to be in when called.
$mock->expects($matcher)
->method($methodName)
->with($param1, $param2);
Calling `with()` without any parameters will force the mock method to accept no parameters.
PHPUnit has a fairly complex way of comparing parameters passed to the mock method with the expected values, which can be summarised like so -
* If the values are identical, they are equal
* If the values are of different types they are not equal
* If the values are numbers they they are considered equal if their difference is equal to zero (this level of accuracy can be changed)
* If the values are objects then they are converted to arrays and are compared as arrays
* If the values are arrays then any sub-arrays deeper than x levels (default 10) are ignored in the comparision
* If the values are arrays and one contains more than elements that the other (at any depth up to the max depth), then they are not equal
#### More advanced parameter comparisions
Sometimes you need to be more specific about how PHPUnit should compare parameters, i.e. if you want to make sure that one of the parameters is an instance of an object, yet isn't necessarily identical to a particular instance.
In PHPUnit, the logic for validating objects and datatypes has been refactored into "constraint objects". If you look in any of the assertX() methods you can see that they are nothing more than wrappers for associating constraint objects with tests.
If a parameter passed to `with()` is not an instance of a constraint object (one which extends `PHPUnit_Framework_Constraint`) then PHPUnit creates a new `IsEqual` comparision object for it.
i.e., the following methods produce the same result:
->with('foo', 1);
->with($this->equalTo('foo'), $this->equalTo(1));
Here are some of the wrappers PHPUnit provides for creating constraint objects:
`$this->arrayHasKey($key)`
: Asserts that the parameter will have an element with index `$key`
`$this->attribute(PHPUnit_Framework_Constraint $constraint, $attributeName)`
: Asserts that object attribute `$attributeName` of the parameter will satisfy `$constraint`, where constraint is an instance of a constraint (i.e. `$this->equalTo()`)
`$this->fileExists()`
: Accepts no parameters, asserts that the parameter is a path to a valid file (i.e. `file_exists() === TRUE`)
`$this->greaterThan($value)`
: Asserts that the parameter is greater than `$value`
`$this->anything()`
: Returns TRUE regardless of what the parameter is
`$this->equalTo($value, $delta = 0, $canonicalizeEOL = FALSE, $ignoreCase = False)`
: Asserts that the parameter is equal to `$value` (same as not passing a constraint object to `with()`)
: `$delta` is the degree of accuracy to use when comparing numbers. i.e. 0 means numbers need to be identical, 1 means numbers can be within a distance of one from each other
: If `$canonicalizeEOL` is TRUE then all newlines in string values will be converted to `\n` before comparision
: If `$ignoreCase` is TRUE then both strings will be converted to lowercase before comparision
`$this->identicalTo($value)`
: Asserts that the parameter is identical to `$value`
`$this->isType($type)`
: Asserts that the parameter is of type `$type`, where `$type` is a string representation of the core PHP data types
`$this->isInstanceOf($className)`
: Asserts that the parameter is an instance of `$className`
`$this->lessThan($value)`
: Asserts that the parameter is less than `$value`
`$this->objectHasAttribute($attribute)`
: Asserts that the paramater (which is assumed to be an object) has an attribute `$attribute`
`$this->matchesRegularExpression($pattern)`
: Asserts that the parameter matches the PCRE pattern `$pattern` (using `preg_match()`)
`$this->stringContains($string, $ignoreCase = FALSE)`
: Asserts that the parameter contains the string `$string`. If `$ignoreCase` is TRUE then a case insensitive comparision is done
`$this->stringEndsWith($suffix)`
: Asserts that the parameter ends with `$suffix` (assumes parameter is a string)
`$this->stringStartsWith($prefix)`
: Asserts that the parameter starts with `$prefix` (assumes parameter is a string)
`$this->contains($value)`
: Asserts that the parameter contains at least one value that is identical to `$value` (assumes parameter is array or `SplObjectStorage`)
`$this->containsOnly($type, $isNativeType = TRUE)`
: Asserts that the parameter only contains items of type `$type`. `$isNativeType` should be set to TRUE when `$type` refers to a built in PHP data type (i.e. int, string etc.) (assumes parameter is array)
There are more constraint objects than listed here, look in `PHPUnit_Framework_Assert` and `PHPUnit/Framework/Constraint` if you need more constraints.
If we continue our example, we have the following:
$mock->expects($this->once())
->method('check')
->with();
So far PHPUnit knows that we want the `check()` method to be called once, with no parameters. Now we just need to get it to return something...
### What should the method return?
This is the final stage of mocking a method.
By default PHPUnit can return either
* A fixed value
* One of the parameters that were passed to it
* The return value of a specified callback
Specifying a return value is easy, just call `will()` on the object returned by either `method()` or `with()`.
The function is defined like so:
public function will(PHPUnit_Framework_MockObject_Stub $stub)
PHPUnit provides some MockObject stubs out of the box, you can access them via (when called from a testcase):
`$this->returnValue($value)`
: Returns `$value` when the mocked method is called
`$this->returnArgument($argumentIndex)`
: Returns the `$argumentIndex`th argument that was passed to the mocked method
`$this->returnCallback($callback)`
: Returns the value of the callback, useful for more complicated mocking.
: `$callback` should a valid callback (i.e. `is_callable($callback) === TRUE`). PHPUnit will pass the callback all of the parameters that the mocked method was passed, in the same order / argument index (i.e. the callback is invoked by `call_user_func_array()`).
: You can usually create the callback in your testcase, as long as doesn't begin with "test"
Obviously if you really want to you can create your own MockObject stub, but these three should cover most situations.
Updating our example gives:
$mock->expects($this->once())
->method('check')
->with()
->will($this->returnValue(TRUE));
And we're done!
If you now call `$mock->check()` the value TRUE should be returned.
If you don't call a mocked method and PHPUnit expects it to be called then the test the mock was generated for will fail.
<!--
### What about if the mocked method should change everytime its called?
-->

View File

@@ -0,0 +1,117 @@
# Usage
$ phpunit --bootstrap=modules/unittest/bootstrap.php modules/unittest/tests.php
Alternatively you can use a phpunit.xml to have a more fine grained control over which tests are included and which files are whitelisted.
Make sure you only whitelist the highest files in the cascading filesystem, else you could end up with a lot of "class cannot be redefined" errors.
If you use the tests.php testsuite loader then it will only whitelist the highest files. see config/unittest.php for details on configuring the tests.php whitelist.
## Writing tests
If you're writing a test for your application, place it in "application/tests". Similarly, if you're writing a test for a module place it in modules/[modulefolder]/tests
Rather than tell you how to write tests I'll point you in the direction of the [PHPUnit Manual](http://www.phpunit.de/manual/3.4/en/index.html). One thing you should bear in mind when writing tests is that testcases should extend Unittest_Testcase rather than PHPUnit_Framework_TestCase, doing so gives you access to useful kohana specific helpers such as `setEnvironment()`.
Here's a taster of some of the cool things you can do with phpunit:
### Data Providers
Sometimes you want to be able to run a specific test with different sets of data to try and test every eventuality
Ordinarily you could use a foreach loop to iterate over an array of test data, however PHPUnit already can take care of this for us rather easily using "Data Providers". A data provider is a function that returns an array of arguments that can be passed to a test.
<?php
Class ReallyCoolTest extends Unittest_TestCase
{
function providerStrLen()
{
return array(
array('One set of testcase data', 24),
array('This is a different one', 23),
);
}
/**
* @dataProvider providerStrLen
*/
function testStrLen($string, $length)
{
$this->assertSame(
$length,
strlen($string)
);
}
}
The key thing to notice is the `@dataProvider` tag in the doccomment, this is what tells PHPUnit to use a data provider. The provider prefix is totally optional but it's a nice standard to identify providers.
For more info see:
* [Data Providers in PHPUnit 3.2](http://sebastian-bergmann.de/archives/702-Data-Providers-in-PHPUnit-3.2.html)
* [Data Providers](http://www.phpunit.de/manual/3.4/en/writing-tests-for-phpunit.html#writing-tests-for-phpunit.data-providers)
### Grouping tests
To allow users to selectively run tests you need to organise your tests into groups. Here's an example test showing how to do this:
<?php
/**
* This is a description for my testcase
*
* @group somegroup
* @group somegroup.morespecific
*/
Class AnotherReallyCoolTest extends Unittest_TestCase
{
/**
* Tests can also be grouped too!
*
* @group somegroup.morespecific.annoyingstuff
*/
function testSomeAnnoyingCase()
{
// CODE!!
}
}
Our convention is to use lowercase group names, with more specific levels in a group seperated by periods. i.e. The Validate helper tests are part of the following groups:
kohana
kohana.validation
kohana.validation.helpers
To actually limit your testing to the "somegroup" group, use:
$ phpunit --boostrap=index.php --group=somegroup modules/unittest/tests.php
This functionality can be used to record which bug reports a test is for:
/**
*
* @group bugs.1477
*/
function testAccountCannotGoBelowZero()
{
// Some arbitary code
}
To see all groups that are available in your code run:
$ phpunit --boostrap=modules/unittest/bootstrap.php --list-groups modules/unittest/tests.php
*Note:* the `--list-groups` switch should appear before the path to the test suite loader
You can also exclude groups while testing using the `--exclude-group` switch. This can be useful if you want to ignore all kohana tests:
$ phpunit --bootstrap=modules/unittest/bootstrap.php --exclude-group=kohana modules/unittest/tests.php
For more info see:
* [Better PHPUnit Group Annotations](http://mikenaberezny.com/2007/09/04/better-phpunit-group-annotations/)
* [TestNG style Grouping of Tests in PHPUnit 3.2](http://sebastian-bergmann.de/archives/697-TestNG-style-Grouping-of-Tests.html)

View File

@@ -0,0 +1,41 @@
# Testing workflows
Having unittests for your application is a nice idea, but unless you actually use them they're about as useful as a chocolate firegaurd. There are quite a few ways of getting tests "into" your development process and this guide aims to cover a few of them.
## Integrating with IDEs
Modern IDEs have come a long way in the last couple of years and ones like netbeans have pretty decent PHP / PHPUnit support.
### Netbeans (6.8+)
*Note:* Netbeans runs under the assumption that you only have one tests folder per project.
If you want to run tests across multiple modules it might be best creating a separate project for each module.
0. Install the unittest module
1. Open the project which you want to enable phpunit testing for.
2. Now open the project's properties dialouge and in the "Tests Dir" field enter the path to your module's (or application's) test directory.
In this case the only tests in this project are within the unittest module
3. Select the phpunit section from the left hand pane and in the area labelled bootstrap enter the path to your app's index.php file
You can also specify a custom test suite loader (enter the path to your tests.php file) and/or a custom configuration file (enter the path to your phpunit.xml file)
## Looping shell
If you're developing in a text editor such as textmate, vim, gedit etc. chances are phpunit support isn't natively supported by your editor.
In such situations you can run a simple bash script to loop over the tests every X seconds, here's an example script:
while(true) do clear; phpunit; sleep 8; done;
You will probably need to adjust the timeout (`sleep 8`) to suit your own workflow, but 8 seconds seems to be about enough time to see what's erroring before the tests are re-run.
In the above example we're using a phpunit.xml config file to specify all the unit testing settings & to reduce the complexity of the looping script.
## Continuous Integration (CI)
Continuous integration is a team based tool which enables developers to keep tabs on whether changes committed to a project break the application. If a commit causes a test to fail then the build is classed as "broken" and the CI server then alerts developers by email, RSS, IM or glowing (bears|lava lamps) to the fact that someone has broken the build and that all hell's broken lose.
The two more popular CI servers are [Hudson](https://hudson.dev.java.net/) and [phpUnderControl](http://www.phpundercontrol.org/about.html), both of which use [Phing](http://phing.info/) to run the build tasks for your application.

View File

@@ -0,0 +1,21 @@
# Troubleshooting
## I get the error "Class Kohana_Tests could not be found" when testing from the CLI
You need to running PHPUnit >= 3.4, there is a bug in 3.3 which causes this.
## Some of my classes aren't getting whitelisted for code coverage even though their module is
Only the "highest" files in the cascading filesystem are whitelisted for code coverage.
To test your module's file, remove the higher file from the cascading filesystem by disabling their respective module.
A good way of testing is to create a "vanilla" testing environment for your module, devoid of anything that isn't required by the module.
## I get a blank page when trying to generate a code coverage report
Try the following:
1. Generate a html report from the command line using `phpunit {bootstrap info} --coverage-html ./report {insert path to tests.php}`. If any error messages show up, fix them and try to generate the report again
2. Increase the php memory limit
3. Make sure that display_errors is set to "on" in your php.ini config file (this value can sometimes be overriden in a .htaccess file)