如何增强C.R.A.P.类开关功能的索引?

时间:2018-08-18 23:22:44

标签: php unit-testing phpunit code-coverage cyclomatic-complexity

我有一个非常典型的类似于开关函数,该函数针对给定的输入值(在这种情况下为“身体质量指数”)返回分类符。 (我正在使用此功能,但可以是其他任何性质相同的东西)

现在,它非常像这样:

// ...

const TYPE_SEVERE_THINNESS = -3;
const TYPE_MODERATE_THINNESS = -2;
const TYPE_MILD_THINNESS = -1;
const TYPE_REGULAR = 0;
const TYPE_OVERWEIGHT = 1;
const TYPE_PRE_OBESE = 2;
const TYPE_OBESE_GRADE_I = 3;
const TYPE_OBESE_GRADE_II = 4;
const TYPE_OBESE_GRADE_III = 5;

// ...

public static function classification(float $bmi) : int
{
    if ($bmi <= 16.00) {
        return self::TYPE_SEVERE_THINNESS;
    }

    if ($bmi <= 16.99) {
        return self::TYPE_MODERATE_THINNESS;
    }

    if ($bmi <= 18.49) {
        return self::TYPE_MILD_THINNESS;
    }

    if ($bmi <= 24.99) {
        return self::TYPE_REGULAR;
    }

    if ($bmi <= 27.49) {
        return self::TYPE_OVERWEIGHT;
    }

    if ($bmi <= 29.99) {
        return self::TYPE_PRE_OBESE;
    }

    if ($bmi <= 34.99) {
        return self::TYPE_OBESE_GRADE_I;
    }

    if ($bmi <= 39.99) {
        return self::TYPE_OBESE_GRADE_II;
    }

    if ($bmi >= 40) {
        return self::TYPE_OBESE_GRADE_III;
    }
}

我要进行重构,正在考虑对该功能的所有可能的增强,特别是用于降低C.R.A.P.索引( Change Risk Anti-Patterns ),目前正在返回值110.00

当然,可能会有许多可能的增强。随时提出建议。

但是我的问题特别是关于降低cycolmatic复杂性,

a)还有其他方法来构造此代码,以便C.R.A.P.指数走低? b)为了正确测试此功能,我应该生成一个断言每种情况的测试,还是执行许多测试以解决每种可能的情况? (我现在的答案可能是“由您决定”,但是也许存在一种更好的方法来降低圈复杂度,从而让数量较少的测试仍然可以涵盖所有或大多数可能的情况。)

如果必须匹配相等的值,我只会使用一个hashmap(键值数组),但是由于我正在评估范围,因此方法可能会有所不同。

更新:在构建了包含每种方案示例的测试案例后,CRAP索引降至10.01。不过,我相信还有另一种执行值查找的方法。

/**
 * Test it returns a valid WHO classification for BMI type
 *
 * @return void
 */
public function test_it_returns_a_valid_who_classification_for_bmi_type()
{
    // Sample bmi => expected type
    // Key must be a string later converted to float
    $testMatrix = [
        "15" => BMILevel::TYPE_SEVERE_THINNESS,
        "16.5" => BMILevel::TYPE_MODERATE_THINNESS,
        "18" => BMILevel::TYPE_MILD_THINNESS,
        "24" => BMILevel::TYPE_REGULAR,
        "27" => BMILevel::TYPE_OVERWEIGHT,
        "29" => BMILevel::TYPE_PRE_OBESE,
        "34" => BMILevel::TYPE_OBESE_GRADE_I,
        "39" => BMILevel::TYPE_OBESE_GRADE_II,
        "41" => BMILevel::TYPE_OBESE_GRADE_III,
    ];

    foreach ($testMatrix as $bmi => $categoryCheck) {
        $type = BMILevel::classification(floatval($bmi));

        $this->assertEquals($type, $categoryCheck);
    }
}

2 个答案:

答案 0 :(得分:1)

测试通常可以让您反映现有的(也许是过时的)实现:

const TYPE_SEVERE_THINNESS = -3;
const TYPE_MODERATE_THINNESS = -2;
const TYPE_MILD_THINNESS = -1;
const TYPE_REGULAR = 0;
const TYPE_OVERWEIGHT = 1;
const TYPE_PRE_OBESE = 2;
const TYPE_OBESE_GRADE_I = 3;
const TYPE_OBESE_GRADE_II = 4;
const TYPE_OBESE_GRADE_III = 5;

所以您在这里是一个有序列表(从上到下):

const TYPES = [-3, -2, -1, 0, 1, 2, 3, 4, 5];

或者也许:

const TYPES = [1, 2, 3, 4, 5, 6, 7, 8, 9];
const TYPE_REFERENCE = 4;

和相应的值(不是键,不是转换的问题):

const VALUES = [15, 16.5, 18, 24, 27, 29, 34, 39, 41];

,您可能要提供标签:

const LABELS = ["Severe Thinness", "Moderate Thinness", "Mild Thinness", 
                "Regular", "Overweight", "Pre-Obese", "Obese Grade I",
                "Obese Grade II", "Obese Grade III"];

因此,可以轻松想象的是,大多数不属于代码,而是属于用于配置代码的数据。然后,单元测试可以针对不同的数据集进行测试,这不仅可以提高被测系统的稳定性,而且可以提高系统的稳定性。 测试哪些扩展名(更改)可以很容易地应用现有代码。

在测试中使用数据提供者通常会表明,代码库本身最有可能应该具有一些数据提供者,而不是那么硬编码。

答案 1 :(得分:0)

好的,我设法获得了相当合理的C.R.A.P.重构后索引,同时保持测试绿色。

我将函数转换为具有上限(自下而上)的查找。我需要为超出范围的值添加一个额外的大小写,并覆盖该大小写。

代码:

public static function classification(float $bmi) : int
{
    $classifications = [
        ['limit' => 16.0 , 'type' => self::TYPE_SEVERE_THINNESS],
        ['limit' => 16.99, 'type' => self::TYPE_MODERATE_THINNESS],
        ['limit' => 18.49, 'type' => self::TYPE_MILD_THINNESS],
        ['limit' => 24.99, 'type' => self::TYPE_REGULAR],
        ['limit' => 27.49, 'type' => self::TYPE_OVERWEIGHT],
        ['limit' => 29.99, 'type' => self::TYPE_PRE_OBESE],
        ['limit' => 34.99, 'type' => self::TYPE_OBESE_GRADE_I],
        ['limit' => 39.99, 'type' => self::TYPE_OBESE_GRADE_II],
        ['limit' => 60   , 'type' => self::TYPE_OBESE_GRADE_III],
    ];

    foreach ($classifications as $classification) {
        if ($bmi <= $classification['limit']) {
            return $classification['type'];
        }
    }

    return self::TYPE_OBESE_GRADE_III;
}

测试:

/**
 * Test it returns a valid WHO classification for BMI type
 *
 * @return void
 */
public function test_it_returns_a_valid_who_classification_for_bmi_type()
{
    // Sample bmi => expected type
    // Key must be a string later converted to float
    $testMatrix = [
        "15" => BMILevel::TYPE_SEVERE_THINNESS,
        "16.5" => BMILevel::TYPE_MODERATE_THINNESS,
        "18" => BMILevel::TYPE_MILD_THINNESS,
        "24" => BMILevel::TYPE_REGULAR,
        "27" => BMILevel::TYPE_OVERWEIGHT,
        "29" => BMILevel::TYPE_PRE_OBESE,
        "34" => BMILevel::TYPE_OBESE_GRADE_I,
        "39" => BMILevel::TYPE_OBESE_GRADE_II,
        "41" => BMILevel::TYPE_OBESE_GRADE_III,
        "100" => BMILevel::TYPE_OBESE_GRADE_III, // After upper bound limit
    ];

    foreach ($testMatrix as $bmi => $categoryCheck) {
        $type = BMILevel::classification(floatval($bmi));

        $this->assertEquals($type, $categoryCheck);
    }
}

仍然欢迎有关如何增强功能的提示。