GenerateTestClass   B
last analyzed

Complexity

Total Complexity 37

Size/Duplication

Total Lines 344
Duplicated Lines 7.27 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 97.87%

Importance

Changes 1
Bugs 1 Features 0
Metric Value
wmc 37
c 1
b 1
f 0
lcom 1
cbo 3
dl 25
loc 344
ccs 138
cts 141
cp 0.9787
rs 8.6

9 Methods

Rating   Name   Duplication   Size   Complexity  
B generate() 0 31 1
C generateClassProperties() 0 41 7
B generateSetUp() 13 43 5
A getConstructorParameters() 0 10 2
A generateTestMethods() 0 18 3
B generateTestMethodBody() 12 36 5
C generateUseStatements() 0 40 8
A isMethodTestable() 0 8 3
A getPHPUnitMockMethod() 0 12 3

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

1
<?php
2
3
declare(strict_types = 1);
4
5
namespace PHPChunkit;
6
7
use Doctrine\Common\Inflector\Inflector;
8
use PHPUnit\Framework\TestCase;
9
use ReflectionClass;
10
use ReflectionMethod;
11
12
/**
13
 * @testClass PHPChunkit\Test\GenerateTestClassTest
14
 */
15
class GenerateTestClass
16
{
17
    const CLASS_TEMPLATE = <<<EOF
18
<?php
19
20
namespace {{ namespace }};
21
22
{{ useStatements }}
23
24
class {{ shortName }} extends TestCase
25
{
26
{{ properties }}
27
28
{{ setUpCode }}
29
30
{{ methods }}
31
}
32
33
EOF;
34
35
    const COMMAND_TEMPLATE = <<<EOF
36
<?php
37
38
namespace {{ namespace }};
39
40
{{ useStatements }}
41
use OpenSky\Bundle\MainBundle\Tests\OpenSkyCommandTestCase;
42
use Symfony\Component\DependencyInjection\Container;
43
44
class {{ shortName }} extends OpenSkyCommandTestCase
45
{
46
    private \$command;
47
    private \$container;
48
49
    protected function setUp()
50
    {
51
       \$this->container = new Container();
52
       \$this->command = new {{ classShortName }}();
53
       \$this->command->setContainer(\$this->container);
54
    }
55
56
    public function testExecute()
57
    {
58
    }
59
}
60
61
EOF;
62
63
    /**
64
     * @var ReflectionClass
65
     */
66
    private $reflectionClass;
67
68
    /**
69
     * @var string
70
     */
71
    private $classShortName;
72
73
    /**
74
     * @var string
75
     */
76
    private $classCamelCaseName;
77
78
    /**
79
     * @var string
80
     */
81
    private $testNamespace;
82
83
    /**
84
     * @var string
85
     */
86
    private $testClassShortName;
87
88
    /**
89
     * @var string
90
     */
91
    private $useStatementsCode;
92
93
    /**
94
     * @var string
95
     */
96
    private $testPropertiesCode;
97
98
    /**
99
     * @var string
100
     */
101
    private $setUpCode;
102
103
    /**
104
     * @var string
105
     */
106
    private $testMethodsCode;
107
108 2
    public function generate(string $className) : string
109
    {
110 2
        $this->reflectionClass = new ReflectionClass($className);
111
112 2
        $this->classShortName = $this->reflectionClass->getShortName();
113 2
        $this->classCamelCaseName = Inflector::camelize($this->classShortName);
114 2
        $this->testNamespace = preg_replace('/(.*)Bundle/', '$0\Tests', $this->reflectionClass->getNamespaceName());
115 2
        $this->testClassShortName = $this->classShortName.'Test';
116
117 2
        $this->useStatementsCode = $this->generateUseStatements();
118 2
        $this->testPropertiesCode = $this->generateClassProperties();
119 2
        $this->setUpCode = $this->generateSetUp();
120 2
        $this->testMethodsCode = $this->generateTestMethods();
121
122 2
        $twig = new \Twig_Environment(new \Twig_Loader_String(), [
123 2
            'autoescape' => false,
124
        ]);
125
126 2
        $template = self::CLASS_TEMPLATE;
127
128 2
        return $twig->render($template, [
129 2
            'classShortName' => $this->classShortName,
130 2
            'classCamelCaseName' => $this->classCamelCaseName,
131 2
            'namespace' => $this->testNamespace,
132 2
            'shortName' => $this->testClassShortName,
133 2
            'methods' => $this->testMethodsCode,
134 2
            'properties' => $this->testPropertiesCode,
135 2
            'useStatements' => $this->useStatementsCode,
136 2
            'setUpCode' => $this->setUpCode,
137
        ]);
138
    }
139
140 2
    private function generateClassProperties() : string
141
    {
142 2
        $testPropertiesCode = [];
143
144 2
        if ($parameters = $this->getConstructorParameters()) {
145 1
            foreach ($parameters as $key => $parameter) {
146 1
                $isLast = $key === count($parameters) - 1;
147
148 1
                if ($parameterClass = $parameter->getClass()) {
149 1
                    $testPropertiesCode[] = '    /**';
150 1
                    $testPropertiesCode[] = '     * @var '.$parameterClass->getShortName();
151 1
                    $testPropertiesCode[] = '     */';
152 1
                    $testPropertiesCode[] = '    private $'.$parameter->name.';';
153
154 1
                    if (!$isLast) {
155 1
                        $testPropertiesCode[] = '';
156
                    }
157
                } else {
158 1
                    $testPropertiesCode[] = '    /**';
159 1
                    $testPropertiesCode[] = '     * @var TODO';
160 1
                    $testPropertiesCode[] = '     */';
161 1
                    $testPropertiesCode[] = '    private $'.$parameter->name.';';
162
163 1
                    if (!$isLast) {
164 1
                        $testPropertiesCode[] = '';
165
                    }
166
                }
167
            }
168
        }
169
170 2
        if (!empty($parameters)) {
171 1
            $testPropertiesCode[] = '';
172
        }
173
174 2
        $testPropertiesCode[] = '    /**';
175 2
        $testPropertiesCode[] = '     * @var '.$this->classShortName;
176 2
        $testPropertiesCode[] = '     */';
177 2
        $testPropertiesCode[] = '    private $'.$this->classCamelCaseName.';';
178
179 2
        return implode("\n", $testPropertiesCode);
180
    }
181
182 2
    private function generateSetUp() : string
183
    {
184 2
        $classShortName = $this->reflectionClass->getShortName();
185 2
        $classCamelCaseName = Inflector::camelize($classShortName);
186
187 2
        $setUpCode = [];
188 2
        $setUpCode[] = '    protected function setUp()';
189 2
        $setUpCode[] = '    {';
190
191 2
        if ($parameters = $this->getConstructorParameters()) {
192 1 View Code Duplication
            foreach ($parameters as $parameter) {
193 1
                if ($parameterClass = $parameter->getClass()) {
194 1
                    $setUpCode[] = sprintf('        $this->%s = $this->%s(%s::class);',
195 1
                        $parameter->name,
196 1
                        $this->getPHPUnitMockMethod(),
197 1
                        $parameterClass->getShortName()
198
                    );
199
                } else {
200 1
                    $setUpCode[] = sprintf("        \$this->%s = ''; // TODO",
201 1
                        $parameter->name
202
                    );
203
                }
204
            }
205
206 1
            $setUpCode[] = '';
207 1
            $setUpCode[] = sprintf('        $this->%s = new %s(', $classCamelCaseName, $classShortName);
208
209
            // arguments for class being tested
210 1
            $setUpCodeArguments = [];
211 1
            foreach ($parameters as $parameter) {
212 1
                $setUpCodeArguments[] = sprintf('            $this->%s', $parameter->name);
213
            }
214 1
            $setUpCode[] = implode(",\n", $setUpCodeArguments);
215
216 1
            $setUpCode[] = '        );';
217
        } else {
218 1
            $setUpCode[] = sprintf('        $this->%s = new %s();', $classCamelCaseName, $classShortName);
219
        }
220
221 2
        $setUpCode[] = '    }';
222
223 2
        return implode("\n", $setUpCode);
224
    }
225
226 2
    private function getConstructorParameters() : array
227
    {
228 2
        $constructor = $this->reflectionClass->getConstructor();
229
230 2
        if ($constructor) {
231 1
            return $constructor->getParameters();
232
        }
233
234 1
        return [];
235
    }
236
237 2
    private function generateTestMethods() : string
238
    {
239 2
        $testMethodsCode = [];
240
241 2
        foreach ($this->reflectionClass->getMethods() as $method) {
242 2
            if (!$this->isMethodTestable($method)) {
243 1
                continue;
244
            }
245
246 2
            $testMethodsCode[] = sprintf('    public function test%s()', ucfirst($method->name));
247 2
            $testMethodsCode[] = '    {';
248 2
            $testMethodsCode[] = $this->generateTestMethodBody($method);
249 2
            $testMethodsCode[] = '    }';
250 2
            $testMethodsCode[] = '';
251
        }
252
253 2
        return '    '.trim(implode("\n", $testMethodsCode));
254
    }
255
256 2
    private function generateTestMethodBody(ReflectionMethod $method) : string
257
    {
258 2
        $parameters = $method->getParameters();
259
260 2
        $testMethodBodyCode = [];
261
262 2
        if (!empty($parameters)) {
263 1 View Code Duplication
            foreach ($parameters as $parameter) {
264 1
                if ($parameterClass = $parameter->getClass()) {
265 1
                    $testMethodBodyCode[] = sprintf(
266 1
                        '        $%s = $this->%s(%s::class);',
267 1
                        $parameter->name,
268 1
                        $this->getPHPUnitMockMethod(),
269 1
                        $parameterClass->getShortName()
270
                    );
271
                } else {
272 1
                    $testMethodBodyCode[] = sprintf("        \$%s = '';", $parameter->name);
273
                }
274
            }
275
276 1
            $testMethodBodyCode[] = '';
277 1
            $testMethodBodyCode[] = sprintf('        $this->%s->%s(', $this->classCamelCaseName, $method->name);
278
279 1
            $testMethodParameters = [];
280 1
            foreach ($parameters as $parameter) {
281 1
                $testMethodParameters[] = sprintf('$%s', $parameter->name);
282
            }
283
284 1
            $testMethodBodyCode[] = '            '.implode(",\n            ", $testMethodParameters);
285 1
            $testMethodBodyCode[] = '        );';
286
        } else {
287 1
            $testMethodBodyCode[] = sprintf('        $this->%s->%s();', $this->classCamelCaseName, $method->name);
288
        }
289
290 2
        return implode("\n", $testMethodBodyCode);
291
    }
292
293 2
    private function generateUseStatements() : string
294
    {
295 2
        $dependencies = [];
296 2
        $dependencies[] = $this->reflectionClass->name;
297 2
        $dependencies[] = TestCase::class;
298
299 2
        if ($parameters = $this->getConstructorParameters()) {
300 1
            foreach ($parameters as $parameter) {
301 1
                if (!$parameterClass = $parameter->getClass()) {
302 1
                    continue;
303
                }
304
305 1
                $dependencies[] = $parameterClass->getName();
306
            }
307
        }
308
309 2
        foreach ($this->reflectionClass->getMethods() as $method) {
310 2
            if (!$this->isMethodTestable($method)) {
311 1
                continue;
312
            }
313
314 2
            foreach ($method->getParameters() as $parameter) {
315 1
                if (!$parameterClass = $parameter->getClass()) {
316 1
                    continue;
317
                }
318
319 2
                $dependencies[] = $parameterClass->getName();
320
            }
321
        }
322
323 2
        sort($dependencies);
324
325 2
        $dependencies = array_unique($dependencies);
326
327 2
        $useStatementsCode = array_map(function($dependency) {
328 2
            return sprintf('use %s;', $dependency);
329 2
        }, $dependencies);
330
331 2
        return implode("\n", $useStatementsCode);
332
    }
333
334 2
    private function isMethodTestable(ReflectionMethod $method) : bool
335
    {
336 2
        if ($this->reflectionClass->name !== $method->class) {
337
            return false;
338
        }
339
340 2
        return substr($method->name, 0, 2) !== '__' && $method->isPublic();
341
    }
342
343
    /**
344
     * @return string
345
     */
346 1
    private function getPHPUnitMockMethod()
347
    {
348 1
        foreach (['createMock', 'getMock'] as $method) {
349
            try {
350 1
                new \ReflectionMethod(TestCase::class, $method);
351 1
                return $method;
352
            } catch (\ReflectionException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
353
            }
354
        }
355
356
        throw new \RuntimeException('Unable to detect PHPUnit version');
357
    }
358
}
359