You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何对存在大量可能输出的System Test场景进行优雅测试?

推荐解决方案

一、测试侧优化:分组参数化测试(兼顾可读性、可维护性与全量覆盖)

你提到的两个现有方案的核心痛点是「代码冗余」和「规则不直观」无法兼顾,用**参数化测试用例源(TestCaseSource)**的方案可以完美解决这个问题:
你可以直接把业务代码里的规则逻辑映射成测试用例生成逻辑,既不用手动写15个TestCase注解,也不会出现嵌套foreach那种无法定位问题、规则不透明的问题。

代码示例:

public class SetDifficulty_Tests : MarioTestBase
{
    // 测试用例生成逻辑,和业务规则一一对应,直观可维护
    private static IEnumerable<TestCaseData> GenerateDifficultyTestCases()
    {
        // 规则1:GrassLand 所有敌人难度均为Basic
        foreach (Enemy enemy in Enum.GetValues(typeof(Enemy)))
        {
            yield return new TestCaseData(Level.GrassLand, enemy, Difficulty.Basic);
        }

        // 规则2:DesertLand 所有敌人难度均为Basic
        foreach (Enemy enemy in Enum.GetValues(typeof(Enemy)))
        {
            yield return new TestCaseData(Level.DesertLand, enemy, Difficulty.Basic);
        }

        // 规则3:WaterLand A/B/C难度为Advanced,D/E为Basic
        foreach (Enemy enemy in new[] { Enemy.A, Enemy.B, Enemy.C })
        {
            yield return new TestCaseData(Level.WaterLand, enemy, Difficulty.Advanced);
        }
        foreach (Enemy enemy in new[] { Enemy.D, Enemy.E })
        {
            yield return new TestCaseData(Level.WaterLand, enemy, Difficulty.Basic);
        }
    }

    [Test, TestCaseSource(nameof(GenerateDifficultyTestCases))]
    public void SetDifficulty_ShouldSelectCorrectDifficulty(Level level, Enemy enemy, Difficulty expectedDifficulty)
    {
        // Arrange:不需要Mock Mario,直接实例化赋值更准确
        var mario = new Mario { Level = level };
        testModel.Enemy = enemy;

        // Act
        mario.SetDifficulty(testModel);
        Difficulty actualDifficulty = mario.Difficulty;

        // Assert
        Assert.AreEqual(expectedDifficulty, actualDifficulty);
    }
}

方案优势:

  • 规则和业务代码完全一一对应,后续修改业务逻辑只要同步修改对应规则的生成逻辑即可,维护成本极低
  • 所有组合全量覆盖,运行测试时每个组合会单独显示执行结果,出错可以直接定位到具体的Level+Enemy组合
  • 代码简洁,没有冗余的重复TestCase注解,哪怕后续Level和Enemy枚举新增值,只要更新对应规则就行,不需要逐个加测试用例

二、业务代码侧优化:规则表驱动替代嵌套switch,从根源降低维护成本

如果你的业务里有大量这类多枚举组合匹配的逻辑,可以把嵌套switch改成规则驱动的实现,逻辑更清晰,后续加规则不需要改多层分支,测试也可以直接复用规则表:

代码示例:

[assembly: InternalsVisibleTo("MarioTestBase")]
internal class Mario 
{
    // 规则表,每条规则匹配成功返回对应难度,匹配失败返回null
    private static readonly List<Func<Level, Enemy, Difficulty?>> DifficultyRules = new()
    {
        (level, enemy) => level == Level.GrassLand ? Difficulty.Basic : null,
        (level, enemy) => level == Level.DesertLand ? Difficulty.Basic : null,
        (level, enemy) => level == Level.WaterLand && new[]{Enemy.A, Enemy.B, Enemy.C}.Contains(enemy) ? Difficulty.Advanced : null,
        (level, enemy) => level == Level.WaterLand && new[]{Enemy.D, Enemy.E}.Contains(enemy) ? Difficulty.Basic : null,
    };

    internal void SetDifficulty(InputModel inputModel)
    {
        // 前置参数校验
        if (!Enum.IsDefined(typeof(Level), Level))
            throw new ArgumentException("This level does not exist.");
        if (!Enum.IsDefined(typeof(Enemy), inputModel.Enemy))
            throw new ArgumentException("Enemy does not exist.");
        
        // 匹配规则
        foreach (var rule in DifficultyRules)
        {
            var result = rule(Level, inputModel.Enemy);
            if (result.HasValue)
            {
                Difficulty = result.Value;
                return;
            }
        }

        throw new InvalidOperationException("No matching difficulty rule found.");
    }
}

关于是否需要全量测试的说明

如果符合以下两个条件,我们团队的实践是优先做全量覆盖:

  1. 枚举值都是有限固定的,组合量级在千级以内,生成测试用例的成本极低
  2. 规则迭代频繁,经常会出现修改某个特定组合逻辑的需求,全量覆盖可以避免漏改测试导致的线上问题
    如果组合量级特别大(超过万级),且同组等价类的规则完全不会单独修改,可以只测每个等价类的代表值,但是需要团队达成共识,修改规则时必须同步更新测试的等价类划分。

现有测试的小优化点

你现有测试里Mock了Mario类,但你要测的就是Mario本身的SetDifficulty逻辑,Mock除非设置CallBase否则不会执行实际的业务代码,很容易因为Mock配置错误导致测试失效,直接实例化Mario赋值Level即可,不需要Mock。

内容的提问来源于stack exchange,提问作者Woudjee

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 17:06:03