Skip to content
This repository has been archived by the owner on Feb 13, 2019. It is now read-only.

Commit

Permalink
Comparisons refactor (#1)
Browse files Browse the repository at this point in the history
Updates a fair amount of handling to allow for some saner code. The big takeaway is that we now have a dynamic comparisons system which allows extensions to configure assertions per their needs. For example--

```php
<?php

use Rawebone\Tapped\{Extension, Comparison, Comparator};

class MyExtension extends Extension
{
     public function comparisons(Comparator $c)
     {
          $c->register(new class implements Comparison {
               compare($a, $b) { return $a instanceof $b; }
               name() { return 'toBeAnInstanceOf'; }
          });
     }
}
```

We can then call `$expect(new A)->toBeAnInstanceOf(A::class)`. In total this change might not seem very remarkable. However, it allows users to easily and cleanly extend the assertion functionality without needing to bloat the core system. Additionally this allows for domain specific comparisons to be exposed with domain specific extensions.
  • Loading branch information
nrawe committed Nov 16, 2016
1 parent 3e184d2 commit 91663f8
Show file tree
Hide file tree
Showing 18 changed files with 363 additions and 140 deletions.
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
/vendor/
/.vscode/
/tests/
/.vscode/
46 changes: 45 additions & 1 deletion bin/tapped.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,48 @@

require_once __DIR__ . '/../vendor/autoload.php';

Rawebone\Tapped\kernel()->runTests();
use Rawebone\Tapped\BailOutError;
use function Rawebone\Tapped\Protocol\bailOut;
use function Rawebone\Tapped\Harness\{kernel, comparisons, extensions, environment};

try {
// Initialise the testing environment.
environment(getcwd() . DIRECTORY_SEPARATOR . 'tests');

// Load in the default comparisons which ship with the Framework.
comparisons()->registerMany(
require_once __DIR__ . '/../configuration/comparisons.php'
);

// Load in the default extensions which ship with the Framework.
extensions()->registerMany(
require_once __DIR__ . '/../configuration/extensions.php'
);

// Load in the user requested extensions.
extensions()->registerMany(
environment()->extensions()
);

// Boot all of the extensions.
extensions()->boot();

// Register the comparisons exposed by the extensions.
extensions()->comparisons(comparisons());

// Execute the tests.
kernel()->run(
environment()->testFiles()
);

// Shutdown all of the extensions.
extensions()->shutdown();

} catch (BailOutError $bail) {
// This is a specific error message, so the stack is irrelevant.
bailOut($bail->getMessage());

} catch (Throwable $t) {
// This is a general error, so the stack is important.
bailOut((string)$t);
}
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
"Rawebone\\Tapped\\": "src/"
},
"files": [
"src/Functions/harness.php",
"src/Functions/interface.php",
"src/Functions/comparisons.php",
"src/Functions/protocol.php"
]
},
Expand Down
15 changes: 15 additions & 0 deletions configuration/comparisons.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

/**
* This file contains the default comparisons that are shipped with the
* framework.
*
* This file will be loaded when the framework boots.
*/

use Rawebone\Tapped\Comparisons;

return [
new Comparisons\Equals,
new Comparisons\DoesNotEqual,
];
12 changes: 12 additions & 0 deletions configuration/extensions.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

/**
* This file contains the default extensions that are shipped with the
* framework.
*
* This file will be loaded when the framework boots.
*/

return [

];
38 changes: 20 additions & 18 deletions src/Assertion.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

namespace Rawebone\Tapped;

use function Rawebone\Tapped\Comparisons\comparison;

/**
* Assertion provides a mechanism for fluently testing a value against
* another value.
Expand All @@ -20,6 +18,13 @@ class Assertion
*/
protected $comparison;

/**
* The Comparisons that are available.
*
* @var Comparator
*/
protected $comparator;

/**
* The description of what the assertion is for.
*
Expand Down Expand Up @@ -51,27 +56,19 @@ class Assertion
/**
* Creates a new instance of the Assertion.
*/
public function __construct(Kernel $kernel, $subject)
public function __construct(Kernel $kernel, Comparator $comparator, $subject)
{
$this->kernel = $kernel;
$this->comparator = $comparator;
$this->subject = $subject;
}

/**
* Registers that the value should be compared for equality.
* Dynamically call for the comparison.
*/
public function toEqual($expectation): Assertion
public function __call($name, $args): Assertion
{
return $this->comparison('equals', $expectation);
}


/**
* Registers that the value should be compared for non-equality.
*/
public function toNotEqual($expectation): Assertion
{
return $this->comparison('notEquals', $expectation);
return $this->comparison($name, $args[0]);
}

/**
Expand All @@ -92,17 +89,22 @@ public function when(string $description): Assertion
*/
public function __destruct()
{
$this->kernel->assertion(
comparison($this->subject, $this->expectation, $this->comparison),
$this->description
$result = $this->comparator->compare(
$this->comparison, $this->subject, $this->expectation
);

$this->kernel->assertion($result, $this->description);
}

/**
* Registers the comparison which should be made.
*/
protected function comparison(string $comparison, $expectation): Assertion
{
if (! $this->comparator->has($comparison)) {
throw new BailOutError('Unknown comparison ' . $comparison);
}

$this->comparison = $comparison;
$this->expectation = $expectation;

Expand Down
90 changes: 90 additions & 0 deletions src/Comparator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

namespace Rawebone\Tapped;

use ReflectionClass;

/**
* Comparator acts as the method by which we can make comparisons between values.
*
* To allow for flexible integration with third-party tooling which requires
* bespoke comparisons, we define this as a dynamic handler.
*
* Third parties can then register their comparisons at framework startup for
* use inside the users tests.
*/
class Comparator
{
/**
* The Comparisons that can be made.
*
* @var Comparison[]
*/
protected $comparisons = [];

/**
* Finds the required handler and delegates to it to make the comparison.
*/
public function compare(string $comparison, $subject, $expectation): bool
{
if (!$this->has($comparison)) {
return false;
}

return $this->comparisons[$comparison]->compare($subject, $expectation);
}

/**
* Returns whether the given comparison has been registered.
*/
public function has(string $comparison): bool
{
return array_key_exists($comparison, $this->comparisons);
}

/**
* Registers a comparison for use.
*
* The key by which the Comparison can be accessed will be determined by
* one of two methods--
*
* 1. If the comparison has a `name()` method, then that will be expected
* to return the name in string form.
* 2. Otherwise the short class name will be used, with the first character
* lowercased.
*/
public function register(Comparison $comparison)
{
$name = $this->getNameFor($comparison);

$this->comparisons[$name] = $comparison;
}

/**
* Registers many comparisons for use.
*/
public function registerMany(array $comparisons)
{
foreach ($comparisons as $comparison) {
if (!$comparison instanceof Comparison) {
throw new BailOutError('Invalid comparison provided.');
}

$this->register($comparison);
}
}

/**
* Returns the real name of the comparison.
*/
protected function getNameFor(Comparison $comparison): string
{
if (method_exists($comparison, 'name')) {
return $comparison->name();
}

$reflection = new ReflectionClass($comparison);

return lcfirst($reflection->getShortName());
}
}
12 changes: 12 additions & 0 deletions src/Comparison.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace Rawebone\Tapped;

/**
* A Comparison provides a simple interface for checking whether two values
* are "equal".
*/
interface Comparison
{
public function compare($subject, $expectation): bool;
}
16 changes: 16 additions & 0 deletions src/Comparisons/DoesNotEqual.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

namespace Rawebone\Tapped\Comparisons;

class DoesNotEqual extends Equals
{
public function compare($subject, $expectation): bool
{
return ! parent::compare($subject, $expectation);
}

public function name()
{
return 'toNotEqual';
}
}
18 changes: 18 additions & 0 deletions src/Comparisons/Equals.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

namespace Rawebone\Tapped\Comparisons;

use Rawebone\Tapped\Comparison;

class Equals implements Comparison
{
public function compare($subject, $expectation): bool
{
return $subject === $expectation;
}

public function name()
{
return 'toEqual';
}
}
9 changes: 9 additions & 0 deletions src/Extension.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,15 @@ public function boot()
// noop
}

/**
* This method will be called to register the extension specific
* comparisons.
*/
public function comparisons(Comparator $comparator)
{
// noop
}

/**
* This method will be called just before a test is executed.
*/
Expand Down
Loading

0 comments on commit 91663f8

Please sign in to comment.