Browse Source

[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.
logmanoriginal 3 years ago
parent
commit
6bceb2b2db
3 changed files with 215 additions and 0 deletions
  1. 6 0
      .travis.yml
  2. 16 0
      phpunit.xml
  3. 193 0
      tests/BridgeImplementationTest.php

+ 6 - 0
.travis.yml

@@ -9,6 +9,9 @@ install:
       pear channel-update pear.php.net;
       pear install PHP_CodeSniffer;
     fi
+  - if [[ $TRAVIS_PHP_VERSION == "7.0" ]]; then
+      composer global require phpunit/phpunit ^6;
+    fi
 
 script:
   - phpenv rehash
@@ -17,6 +20,9 @@ script:
     else
       phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p;
     fi
+  - if [[ $TRAVIS_PHP_VERSION == "7.0" ]]; then
+      phpunit --configuration=phpunit.xml --include-path=lib/;
+    fi
 
 matrix:
   fast_finish: true

+ 16 - 0
phpunit.xml

@@ -0,0 +1,16 @@
+<phpunit
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.5/phpunit.xsd"
+	colors="true"
+	processIsolation="false"
+	timeoutForSmallTests="1"
+	timeoutForMediumTests="1"
+	timeoutForLargeTests="6" >
+
+	<testsuites>
+		<testsuite name="Standard test suite">
+		<file>tests/BridgeImplementationTest.php</file>
+		</testsuite>
+	</testsuites>
+
+</phpunit>

+ 193 - 0
tests/BridgeImplementationTest.php

@@ -0,0 +1,193 @@
+<?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(){}
+}