Created
July 4, 2018 20:35
-
-
Save farfromunique/b3375615b1f73de5a0d80ae76b49faaf to your computer and use it in GitHub Desktop.
a messy script to generate project tests.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// this must be placed inside /test/ directory | |
require '../vendor/autoload.php'; | |
require 'helpers.php'; | |
require 'FileClassReflection.php'; | |
require 'TestCreator.php'; | |
if(!class_exists('Symfony\Component\Finder\Finder')) { | |
die('symfony/finder is required'); | |
} | |
if(!class_exists('Symfony\Component\Filesystem\Filesystem')) { | |
die('symfony/filesystem is required'); | |
} | |
$config = [ | |
'project' => [ | |
'namespace' => 'MyProject\\', | |
'dir' => realpath(__DIR__ . '/../src/MyProject'), | |
], | |
'tests' => [ | |
'namespace' => 'MyProject\\Tests\\', | |
'dir' => realpath(__DIR__ . '/../tests') . DIRECTORY_SEPARATOR . 'MyProject', | |
], | |
'stubs' => [ | |
'class' => "<?php declare(strict_types=1); | |
namespace {{namespace}}; | |
use PHPUnit\Framework\TestCase; | |
class {{class}} extends TestCase | |
{ | |
{{methods}} | |
}", | |
'method' => " | |
public function {{method}}() | |
{ | |
// TODO: write tests for {{oClassNamespace}}\{{oClass}}::{{oMethod}} | |
}" | |
] | |
]; | |
$creator = new TestCreator($config); | |
$creator->run(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
use Symfony\Component\Finder\SplFileInfo as SfFileInfo; | |
class FileClassReflection extends ReflectionClass | |
{ | |
private $fileInfo; | |
public function __construct(SfFileInfo $info) | |
{ | |
$this->fileInfo = $info; | |
parent::__construct($this->getClass($info)); | |
} | |
/** | |
* Returns class reflection for the first class in the file. | |
* | |
* @param SfFileInfo $file A Symfony Finder SplFileInfo representing the file | |
* | |
* @return string|false Full class name if found, false otherwise | |
*/ | |
private function getClass(SfFileInfo $file) | |
{ | |
$class = false; | |
$namespace = false; | |
$tokens = token_get_all($file->getContents()); | |
if (1 === count($tokens) && T_INLINE_HTML === $tokens[0][0]) { | |
throw new \InvalidArgumentException(sprintf('The file "%s" does not contain PHP code. Did you forgot to add the "<?php" start tag at the beginning of the file?', $file)); | |
} | |
for ($i = 0; isset($tokens[$i]); ++$i) { | |
$token = $tokens[$i]; | |
if (!isset($token[1])) { | |
continue; | |
} | |
if (true === $class && T_STRING === $token[0]) { | |
return $namespace . '\\' . $token[1]; | |
} | |
if (true === $namespace && T_STRING === $token[0]) { | |
$namespace = $token[1]; | |
while (isset($tokens[++$i][1]) && in_array($tokens[$i][0], array(T_NS_SEPARATOR, T_STRING))) { | |
$namespace .= $tokens[$i][1]; | |
} | |
$token = $tokens[$i]; | |
} | |
if (T_CLASS === $token[0]) { | |
// Skip usage of ::class constant and anonymous classes | |
$skipClassToken = false; | |
for ($j = $i - 1; $j > 0; --$j) { | |
if (!isset($tokens[$j][1])) { | |
break; | |
} | |
if (T_DOUBLE_COLON === $tokens[$j][0] || T_NEW === $tokens[$j][0]) { | |
$skipClassToken = true; | |
break; | |
} elseif (!in_array($tokens[$j][0], array(T_WHITESPACE, T_DOC_COMMENT, T_COMMENT))) { | |
break; | |
} | |
} | |
if (!$skipClassToken) { | |
$class = true; | |
} | |
} | |
if (T_NAMESPACE === $token[0]) { | |
$namespace = true; | |
} | |
} | |
throw new \InvalidArgumentException('No class was found in ' . $file->getRealPath()); | |
} | |
public function getFileInfo(): SfFileInfo | |
{ | |
return $this->fileInfo; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
function str_after($subject, $search){ | |
return $search === '' ? $subject : \array_reverse(\explode($search, $subject, 2))[0]; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
declare(strict_types=1); | |
use Symfony\Component\Filesystem\Filesystem; | |
use Symfony\Component\Finder\Finder; | |
use Symfony\Component\Finder\SplFileInfo as SfFileInfo; | |
class TestCreator | |
{ | |
protected $config; | |
private $fs; | |
public function __construct(array $config) | |
{ | |
$this->config = $config; | |
$this->fs = new Filesystem(); | |
} | |
public function run() | |
{ | |
$this->createTestDirs(); | |
$classes = $this->getProjectClasses(); | |
/** @var FileClassReflection $class */ | |
foreach ($classes as $class) { | |
$this->createTestCase($class); | |
} | |
} | |
protected function createTestDirs() | |
{ | |
$dir = $this->config['tests']['dir']; | |
if (!$this->fs->exists($dir)) { | |
$this->fs->mkdir($dir); | |
} | |
$directories = Finder::create() | |
->in($this->config['project']['dir']) | |
->directories()->getIterator(); | |
foreach ($directories as $directory) { | |
$dir = $this->getTestPath($directory); | |
if (!$this->fs->exists($dir)) { | |
$this->fs->mkdir($dir); | |
} | |
} | |
} | |
protected function getTestPath(SfFileInfo $file) | |
{ | |
return $this->config['tests']['dir'] . str_after( | |
$file->getRealPath(), | |
$this->config['project']['dir'] | |
); | |
} | |
protected function getProjectClasses() | |
{ | |
$files = $this->getProjectFiles(); | |
$classes = []; | |
foreach ($files as $file) { | |
try { | |
$classes[] = new FileClassReflection($file); | |
} catch (Exception $e) { | |
} | |
} | |
return $classes; | |
} | |
/** | |
* @return sfFileInfo[]|Finder | |
*/ | |
protected function getProjectFiles() | |
{ | |
return Finder::create() | |
->in($this->config['project']['dir']) | |
->name('*.php') | |
->files(); | |
} | |
private function createTestCase(FileClassReflection $class) | |
{ | |
echo PHP_EOL; | |
$reflectionMethods = $class->getMethods(ReflectionMethod::IS_PUBLIC); | |
$methods = []; | |
foreach ($reflectionMethods as $method) { | |
if ($method->isPublic() && $method->name[0] !== '_') { | |
$methods[] = $method; | |
} | |
} | |
if (count($methods) === 0) { | |
echo ' > Skipping test for ' . $class->getName() . ' - ( no public methods ) ' . PHP_EOL; | |
return; | |
} | |
$testCase = [ | |
'oNamespace' => $class->getNamespaceName(), | |
'oClass' => $class->getShortName(), | |
'namespace' => $this->getTestNamespace($class), | |
'class' => $class->getShortName() . 'Test', | |
'file' => $this->getTestFileForClass($class) | |
]; | |
if ($this->fs->exists($testCase['file'])) { | |
echo ' > Skipping TestCase for ' . $class->getName() . ' - ( test exists )' . PHP_EOL; | |
return; | |
} | |
$methodTest = $testCase; | |
$testCase['methods'] = ''; | |
/** @var ReflectionMethod $method */ | |
foreach ($methods as $method) { | |
$methodTest['oMethod'] = $method->getShortName(); | |
$methodTest['method'] = 'test' . ucfirst($method->getShortName()); | |
$body = str_replace( | |
['{{oNamespace}}', '{{oClass}}', '{{namespace}}', '{{class}}', '{{file}}', '{{oMethod}}', '{{method}}'], | |
array_values($methodTest), | |
$this->config['stubs']['method'] | |
); | |
$testCase['methods'] .= $body . PHP_EOL . PHP_EOL; | |
} | |
$this->fs->touch($testCase['file']); | |
$stub = $this->config['stubs']['class']; | |
$content = str_replace( | |
['{{oNamespace}}', '{{oClass}}', '{{namespace}}', '{{class}}', '{{file}}', '{{methods}}'], | |
array_values($testCase), | |
$stub | |
); | |
$this->fs->dumpFile($testCase['file'], $content); | |
echo ' > TestCase created ' . $testCase['namespace'] . "\\" . $testCase['class'] . PHP_EOL; | |
} | |
private function getTestNamespace(FileClassReflection $class) | |
{ | |
$namespace = $class->getNamespaceName(); | |
$vendor = $this->config['project']['namespace']; | |
$classNamespace = substr($namespace, strlen($vendor)); | |
$testNamespace = $this->config['tests']['namespace'] . $classNamespace; | |
return $testNamespace; | |
} | |
private function getTestFileForClass(FileClassReflection $class) | |
{ | |
$fileInfo = $class->getFileInfo(); | |
return str_replace('.php', 'Test.php', $this->getTestPath($fileInfo)); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment