Completed
Push — master ( 26fc7b...ce3432 )
by personal
05:03
created

UnitTesting::calculate()   F

Complexity

Conditions 20
Paths 506

Size

Total Lines 148
Code Lines 82

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 20
eloc 82
nc 506
nop 1
dl 0
loc 148
rs 2.98
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
namespace Hal\Metric\System\UnitTesting;
3
4
use Hal\Application\Config\Config;
5
use Hal\Application\Config\ConfigException;
6
use Hal\Metric\Class_\ClassEnumVisitor;
7
use Hal\Metric\Class_\Coupling\ExternalsVisitor;
8
use Hal\Metric\ClassMetric;
9
use Hal\Metric\Metrics;
10
use Hal\Metric\ProjectMetric;
11
use PhpParser\ParserFactory;
12
13
class UnitTesting
14
{
15
16
    /**
17
     * @var array
18
     */
19
    private $files = [];
20
21
    /**
22
     * @var Config
23
     */
24
    private $config;
25
26
    /**
27
     * GitChanges constructor.
28
     * @param array $files
29
     */
30
    public function __construct(Config $config, array $files)
31
    {
32
        $this->files = $files;
33
        $this->config = $config;
34
    }
35
36
    /**
37
     * @param Metrics $metrics
38
     * @throws ConfigException
39
     */
40
    public function calculate(Metrics $metrics)
41
    {
42
43
        if (!$this->config->has('junit')) {
44
            return;
45
        }
46
47
        // parse junit file
48
        $filename = $this->config->get('junit');
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $filename is correct as $this->config->get('junit') (which targets Hal\Application\Config\Config::get()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
49
        if (!file_exists($filename) || !is_readable($filename)) {
50
            throw new ConfigException('JUnit report cannot be read');
51
        }
52
53
        $unitsTests = [];
0 ignored issues
show
Unused Code introduced by
$unitsTests is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
54
        $infoAboutTests = [];
55
        $assertions = 0;
56
        $projectMetric = new ProjectMetric('unitTesting');
57
58
        // injects default value for each metric
59
        foreach ($metrics->all() as $metric) {
60
            $metric->set('numberOfUnitTests', 0);
61
        }
62
63
        // parsing of XML file without any dependency to DomDocument or simpleXML
64
        // we want to be compatible with every platforms. Maybe (probably) that's a really stupid idea, but I want to try it :p
65
        $testsuites = [];
66
        $alreadyParsed = [];
0 ignored issues
show
Unused Code introduced by
$alreadyParsed is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
67
68
        $dom = new \DOMDocument('1.0', 'UTF-8');
69
        $dom->load($filename);
70
        $xpath = new \DOMXpath($dom);
71
72
73
        // JUNIT format
74
        foreach ($xpath->query('//testsuite[@file]') as $suite) {
75
            array_push($testsuites, (object)[
76
                'file' => $suite->getAttribute('file'),
77
                'name' => $suite->getAttribute('name'),
78
                'assertions' => $suite->getAttribute('assertions'),
79
                'time' => $suite->getAttribute('time'),
80
            ]);
81
        }
82
83
        // CODECEPTION format (file is stored in the <testcase> node
84
        foreach ($xpath->query('//testcase[@file]') as $index => $case) {
85
            $suite = $case->parentNode;
86
87
            if ($suite->hasAttribute('file')) {
88
                // avoid duplicates
89
                continue;
90
            }
91
92
            if (isset($testsuites[$case->getAttribute('class')])) {
93
                // codeception does not consider testcase like junit does
94
                continue;
95
            }
96
97
            if ($suite->hasAttribute('assertions')) {
98
                // codeception store assertions in testsuite, not in testcase
99
                // (but it stores classname in the testcase node) oO
100
                // so we will store "assertions" in the first testcase of the testsuite only
101
                $assertions = $case === $suite->firstChild->nextSibling ? $suite->getAttribute('assertions') : 0;
102
            }
103
104
            $testsuites[$case->getAttribute('class')] = (object)[
105
                'file' => $case->getAttribute('file'),
106
                'name' => $case->getAttribute('class'),
107
                'assertions' => $assertions,
108
                'time' => $suite->getAttribute('time'),
109
            ];
110
        }
111
112
        // analyze each unit test
113
        // This code is slow and can be optimized
114
        foreach ($testsuites as $suite) {
115
            $metricsOfUnitTest = new Metrics();
116
            $parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
117
            $traverser = new \PhpParser\NodeTraverser();
118
            $traverser->addVisitor(new \PhpParser\NodeVisitor\NameResolver());
119
            $traverser->addVisitor(new ClassEnumVisitor($metricsOfUnitTest));
120
            $traverser->addVisitor(new ExternalsVisitor($metricsOfUnitTest));
121
122
            if (!file_exists($suite->file) || !is_readable($suite->file)) {
123
                throw new \LogicException('Cannot find source file referenced in testsuite: ' . $suite->file);
124
            }
125
126
            $code = file_get_contents($suite->file);
127
            $stmts = $parser->parse($code);
128
            $traverser->traverse($stmts);
0 ignored issues
show
Bug introduced by
It seems like $stmts defined by $parser->parse($code) on line 127 can also be of type null; however, PhpParser\NodeTraverser::traverse() does only seem to accept array<integer,object<PhpParser\Node>>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
129
130
            if (!$metricsOfUnitTest->has($suite->name)) {
131
                continue;
132
            }
133
134
            // list of externals sources of unit test
135
            $metric = $metricsOfUnitTest->get($suite->name);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $metric is correct as $metricsOfUnitTest->get($suite->name) (which targets Hal\Metric\Metrics::get()) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
136
            $externals = (array)$metric->get('externals');
137
138
            // global stats for each test
139
            $infoAboutTests[$suite->name] = (object)[
140
                'nbExternals' => sizeof(array_unique($externals)),
141
                'externals' => array_unique($externals),
142
                'filename' => $suite->file,
143
                'classname' => $suite->name,
144
                'assertions' => $suite->assertions,
145
                'time' => $suite->time,
146
            ];
147
148
            $assertions += $suite->assertions;
149
150
            foreach ($externals as $external) {
151
152
                // search for this external in metrics
153
                if (!$metrics->has($external)) {
154
                    continue;
155
                }
156
157
                // SUT (tested class) has unit test
158
                $numberOfUnitTest = $metrics->get($external)->get('numberOfUnitTests');
0 ignored issues
show
Bug introduced by
The method get cannot be called on $metrics->get($external) (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
159
                $numberOfUnitTest++;
160
                $metrics->get($external)->set('numberOfUnitTests', $numberOfUnitTest);
0 ignored issues
show
Bug introduced by
The method set cannot be called on $metrics->get($external) (of type null).

Methods can only be called on objects. This check looks for methods being called on variables that have been inferred to never be objects.

Loading history...
161
            }
162
        }
163
164
        // statistics
165
        $sum = 0;
166
        $nb = 0;
167
        foreach ($metrics->all() as $metric) {
168
            if (!$metric instanceof ClassMetric) {
169
                continue;
170
            }
171
172
            $sum++;
173
            if ($metric->get('numberOfUnitTests') > 0) {
174
                $nb++;
175
            }
176
        }
177
178
        $projectMetric->set('assertions', $assertions);
179
        $projectMetric->set('tests', $infoAboutTests);
180
        $projectMetric->set('nbSuites', sizeof($testsuites));
181
        $projectMetric->set('nbCoveredClasses', $nb);
182
        $projectMetric->set('percentCoveredClasses', round($nb / max($sum, 1) * 100, 2));
183
        $projectMetric->set('nbUncoveredClasses', $sum - $nb);
184
        $projectMetric->set('percentUncoveredClasses', round(($sum - $nb) / max($sum, 1) * 100, 2));
185
186
        $metrics->attach($projectMetric);
187
    }
188
}
189