Completed
Push — master ( bdaaad...4f4372 )
by personal
06:57 queued 04:36
created

UnitTesting::calculate()   D

Complexity

Conditions 14
Paths 90

Size

Total Lines 99
Code Lines 56

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
eloc 56
nc 90
nop 1
dl 0
loc 99
rs 4.9516
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 = [];
54
        $infoAboutTests = [];
55
        $projectMetric = new ProjectMetric('unitTesting');
56
57
        // injects default value for each metric
58
        foreach ($metrics->all() as $metric) {
59
            $metric->set('numberOfUnitTests', 0);
60
        }
61
62
        // parsing of XML file without any dependency to DomDocument or simpleXML
63
        // we want to be compatible with every platforms
64
        $xml = file_get_contents($filename);
65
        if (preg_match_all('!<testsuite name="(.*?)" file="(.*?)"!i', $xml, $matches, PREG_SET_ORDER)) {
66
            foreach ($matches as $m) {
67
                list(, $classname, $fileOfUnitTest) = $m;
68
                $unitsTests[$fileOfUnitTest] = $classname;
69
            }
70
        }
71
72
        // analyze each unit test
73
        // This code is slow and can be optimized
74
        foreach ($unitsTests as $filename => $classname) {
75
            $metricsOfUnitTest = new Metrics();
76
            $parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
77
            $traverser = new \PhpParser\NodeTraverser();
78
            $traverser->addVisitor(new \PhpParser\NodeVisitor\NameResolver());
79
            $traverser->addVisitor(new ClassEnumVisitor($metricsOfUnitTest));
80
            $traverser->addVisitor(new ExternalsVisitor($metricsOfUnitTest));
81
82
            $code = file_get_contents($filename);
83
            $stmts = $parser->parse($code);
84
            $traverser->traverse($stmts);
0 ignored issues
show
Bug introduced by
It seems like $stmts defined by $parser->parse($code) on line 83 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...
85
86
            if (!$metricsOfUnitTest->has($classname)) {
87
                continue;
88
            }
89
90
            // list of externals sources of unit test
91
            $metric = $metricsOfUnitTest->get($classname);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $metric is correct as $metricsOfUnitTest->get($classname) (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...
92
            $externals = (array)$metric->get('externals');
93
94
            // global stats for each test
95
            $infoAboutTests[$classname] = (object)[
96
                'nbExternals' => sizeof(array_unique($externals)),
97
                'externals' => array_unique($externals),
98
                'filename' => $fileOfUnitTest,
0 ignored issues
show
Bug introduced by
The variable $fileOfUnitTest does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
99
                'classname' => $classname
100
            ];
101
102
            foreach ($externals as $external) {
103
104
                // search for this external in metrics
105
                if (!$metrics->has($external)) {
106
                    continue;
107
                }
108
109
                // SUT (tested class) has unit test
110
                $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...
111
                $numberOfUnitTest++;
112
                $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...
113
            }
114
        }
115
116
        // statistics
117
        $sum = 0;
118
        $nb = 0;
119
        foreach ($metrics->all() as $metric) {
120
            if (!$metric instanceof ClassMetric) {
121
                continue;
122
            }
123
124
            $sum++;
125
            if ($metric->get('numberOfUnitTests') > 0) {
126
                $nb++;
127
            }
128
        }
129
130
        $projectMetric->set('tests', $infoAboutTests);
131
        $projectMetric->set('nbTests', sizeof($unitsTests));
132
        $projectMetric->set('nbCoveredClasses', $nb);
133
        $projectMetric->set('percentCoveredClasses', round($nb / $sum * 100, 2));
134
        $projectMetric->set('nbUncoveredClasses', $sum - $nb);
135
        $projectMetric->set('percentUncoveredClasses', round(($sum - $nb) / $sum * 100, 2));
136
137
        $metrics->attach($projectMetric);
138
    }
139
}
140