rss-bridge/tests/BridgeImplementationTest.php
logmanoriginal 6bceb2b2db [tests] Add unit test for bridge implementation
Adds unit test for bridge implementations:

- Custom functions must be in protected or private scope
- getName() must return a valid string (non-empty)
- getURI() must return a valid URI
- Each bridge must define constants for NAME, URI, DESCRIPTION and
  MAINTAINER. CACHE_TIMEOUT and PARAMETERS are optional.

The unit test is written for PHPUnit 6.x and will automatically be
tested by Travis-CI for PHP 7.0 (see .travis.yml).

Remarks:

Unit tests for bridge data were scrapped in #378 for complexity
reasons (tests would have to be maintained for each bridge). This
unit test, however, is written for testing all bridges without
taking specific implementation details into account.
2018-08-09 17:04:16 +02:00

193 lines
4.8 KiB
PHP

<?php
use PHPUnit\Framework\TestCase;
use PHPUnit\Framework\TestResult;
use PHPUnit\Framework\AssertionFailedError;
require_once('Bridge.php');
require_once('BridgeAbstract.php');
require_once('FeedExpander.php');
Bridge::setDir(__DIR__ . '/../bridges/');
/**
* This class checks bridges for implementation details:
*
* - A bridge must not implement public functions other than the ones specified
* by the bridge interfaces. Custom functions must be defined in private or
* protected scope.
* - getName() must return a valid string (non-empty)
* - getURI() must return a valid URI
* - A bridge must define constants for NAME, URI, DESCRIPTION and MAINTAINER,
* CACHE_TIMEOUT and PARAMETERS are optional
*/
final class BridgeImplementationTest extends TestCase {
private function CheckBridgePublicFunctions($bridgeName){
$parent_methods = array();
if(in_array('BridgeInterface', class_parents($bridgeName))) {
$parent_methods = array_merge($parent_methods, get_class_methods('BridgeInterface'));
}
if(in_array('BridgeAbstract', class_parents($bridgeName))) {
$parent_methods = array_merge($parent_methods, get_class_methods('BridgeAbstract'));
}
if(in_array('FeedExpander', class_parents($bridgeName))) {
$parent_methods = array_merge($parent_methods, get_class_methods('FeedExpander'));
}
// Receive all non abstract methods
$methods = array_diff(get_class_methods($bridgeName), $parent_methods);
$method_names = implode(', ', $methods);
$errmsg = $bridgeName
. ' implements additional public method(s): '
. $method_names
. '! Custom functions must be defined in private or protected scope!';
$this->assertEmpty($method_names, $errmsg);
}
private function CheckBridgeGetNameDefaultValue($bridgeName){
if(in_array('BridgeAbstract', class_parents($bridgeName))) { // Is bridge
if(!$this->isFunctionMemberOf($bridgeName, 'getName'))
return;
$bridge = new $bridgeName();
$abstract = new BridgeAbstractTest();
$message = $bridgeName . ': \'getName\' must return a valid name!';
$this->assertNotEmpty(trim($bridge->getName()), $message);
}
}
// Checks whether the getURI function returns empty or default values
private function CheckBridgeGetURIDefaultValue($bridgeName){
if(in_array('BridgeAbstract', class_parents($bridgeName))) { // Is bridge
if(!$this->isFunctionMemberOf($bridgeName, 'getURI'))
return;
$bridge = new $bridgeName();
$abstract = new BridgeAbstractTest();
$message = $bridgeName . ': \'getURI\' must return a valid URI!';
$this->assertNotEmpty(trim($bridge->getURI()), $message);
}
}
private function CheckBridgePublicConstants($bridgeName){
// Assertion only works for BridgeAbstract!
if(in_array('BridgeAbstract', class_parents($bridgeName))) {
$ref = new ReflectionClass($bridgeName);
$constants = $ref->getConstants();
$ref = new ReflectionClass('BridgeAbstract');
$parent_constants = $ref->getConstants();
foreach($parent_constants as $key => $value) {
$this->assertArrayHasKey($key, $constants, 'Constant ' . $key . ' missing in ' . $bridgeName);
// Skip optional constants
if($key !== 'PARAMETERS' && $key !== 'CACHE_TIMEOUT') {
$this->assertNotEquals($value, $constants[$key], 'Constant ' . $key . ' missing in ' . $bridgeName);
}
}
}
}
private function isFunctionMemberOf($bridgeName, $functionName){
$bridgeReflector = new ReflectionClass($bridgeName);
$bridgeMethods = $bridgeReflector->GetMethods();
$bridgeHasMethod = false;
foreach($bridgeMethods as $method) {
if($method->name === $functionName && $method->class === $bridgeReflector->name) {
return true;
}
}
return false;
}
public function testBridgeImplementation($bridgeName){
require_once('bridges/' . $bridgeName . '.php');
$this->CheckBridgePublicFunctions($bridgeName);
$this->CheckBridgePublicConstants($bridgeName);
$this->CheckBridgeGetNameDefaultValue($bridgeName);
$this->CheckBridgeGetURIDefaultValue($bridgeName);
}
public function count() {
return count(Bridge::listBridges());
}
public function run(TestResult $result = null) {
if ($result === null) {
$result = new TestResult;
}
foreach (Bridge::listBridges() as $bridge) {
$bridge .= 'Bridge';
$result->startTest($this);
PHP_Timer::start();
$stopTime = null;
try {
$this->testBridgeImplementation($bridge);
} catch (AssertionFailedError $e) {
$stopTime = PHP_Timer::stop();
$result->addFailure($this, $e, $stopTime);
} catch (Exception $e) {
$stopTime = PHP_Timer::stop();
$result->addError($this, $e, $stopTime);
}
if ($stopTime === null) {
$stopTime = PHP_Timer::stop();
}
$result->endTest($this, $stopTime);
}
return $result;
}
}
class BridgeAbstractTest extends BridgeAbstract {
public function collectData(){}
}