【问题标题】:How to enhance C.R.A.P. index for a switch-like function?如何增强 C.R.A.P.类似开关的功能的索引?
【发布时间】:2019-01-25 13:12:07
【问题描述】:

我有一个非常典型的 switch-like 函数,它返回给定输入值的分类(在本例中为身体质量指数)。 (我正在使用这个函数,但它可以是任何其他相同性质的函数)

目前,情况差不多这样:

// ...

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

当然,可能有许多可能的增强功能。欢迎提出建议。

但我的问题具体是关于降低循环复杂度,

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);
    }
}

【问题讨论】:

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


    【解决方案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"];
    

    所以很容易想象这最不属于代码,而是用于配置代码的数据。然后单元测试可以针对不同的数据集进行测试,这不仅可以增加被测系统的稳定性,还可以 测试哪些扩展(更改)易于使用现有代码应用。

    在测试中使用数据提供者通常可以看出代码库本身很可能应该有一些数据提供者,而不是太多的硬编码。

    【讨论】:

    • 感谢您的澄清!!
    【解决方案2】:

    好的,我设法得到了一个相当合理的 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);
        }
    }
    

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

    【讨论】:

    • 我也会拒绝无效值。
    • 不了解数据提供者。感谢您的提示!
    • 首先想到的是数据提供者。您可以创建两个测试用例,一个用于快乐路径,一个用于完全不快乐或部分不快乐。
    猜你喜欢
    • 2011-12-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-09
    • 1970-01-01
    • 2011-02-28
    • 1970-01-01
    • 1970-01-01
    • 2023-03-31
    相关资源
    最近更新 更多