我有一堆接近100%测试覆盖率的程序集,但我经常遇到如下例所示的情况。我无法测试默认的开关案例,这是为了防范未来的错误,我在枚举中添加了更多项目,但忘记更新switch语句以支持新项目。
我希望能够找到一种模式,我可以在其中消除“不可测试的”代码,测试它或标记代码行(但不是整个方法)被排除在外覆盖率分析。
这可能听起来很愚蠢,但我不想假设默认情况永远不会发生,我不想将默认情况与已经存在的情况捆绑在一起。我希望在创建此类错误时抛出异常。这迟早会发生。
我现在使用 DotCover 来计算报道。
注意:这是只是示例代码,但我认为它说明了一种相当常见的模式。
public class Tester
{
private enum StuffToDo
{
Swim = 0,
Bike,
Run
}
public void DoSomeRandomStuff()
{
var random = new Random();
DoStuff((StuffToDo)random.Next(3));
}
private void DoStuff(StuffToDo stuff)
{
switch (stuff)
{
case StuffToDo.Swim:
break;
case StuffToDo.Bike:
break;
case StuffToDo.Run:
break;
default:
// How do I test or exclude this line from coverage?
throw new ArgumentOutOfRangeException("stuff");
}
}
}
答案 0 :(得分:9)
私有方法总是可以提取到自己的类中。特别是,如果他们持有复杂的逻辑,就像你的那样。我建议您使用StuffDoer
作为公共方法创建DoStuff()
课程,并将其注入Tester
课程。然后你有:
DoStuff()
方法,可通过测试DoSomeRandomStuff()
方法,而不是依赖DoStuff()
的结果,从而使其成为真正的单元测试。答案 1 :(得分:3)
您可以像这样使用DEBUG使用预编译器指令(或创建您自己的):
#if DEBUG
public void DoStuff(StuffToDo stuff)
#else
private void DoStuff(StuffToDo stuff)
#endif
{
switch (stuff)
{
case StuffToDo.Swim:
break;
case StuffToDo.Bike:
break;
case StuffToDo.Run:
break;
default:
// How do I test or exclude this line from coverage?
throw new ArgumentOutOfRangeException("stuff");
}
}
答案 2 :(得分:3)
这样做:
private static int GetRandomStuffInt()
{
var random = new Random();
DoStuff((StuffToDo)random.Next(Enum.GetNames(typeof(StuffToDo)).Length));
}
这将确保它返回的数字是枚举的一部分,因此永远不会到达default:
开关。
(Link)
答案 3 :(得分:2)
更新:在这里给出答案,似乎需要做一些重要的澄清。
DoStuff((StuffToDo)9999)
Func<StuffToDo>
,这会使测试更具可读性。你仍然可以这样做并达到100%:
public class Tester
{
private enum StuffToDo
{
Swim = 0,
Bike,
Run
}
public Func<int> randomStuffProvider = GetRandomStuffInt;
public void DoSomeRandomStuff()
{
DoStuff((StuffToDo)randomStuffProvider());
}
private static int GetRandomStuffInt()
{
//note: don't do this, you're supposed to reuse the random instance
var random = new Random();
return random.Next(3);
}
private void DoStuff(StuffToDo stuff)
{
switch (stuff)
{
case StuffToDo.Swim:
break;
case StuffToDo.Bike:
break;
case StuffToDo.Run:
break;
default:
// How do I test or exclude this line from coverage?
throw new ArgumentOutOfRangeException("stuff");
}
}
}
PS。是的,它暴露了RandomStuffProvider,以便您可以在单元测试中使用。我没有成功Func<StuffToDo>
因为那是私人的。
答案 4 :(得分:2)
我同意上面的Nate。这可能是最简单的(StuffToDo.None)。作为替代方案,您也可以像这样重载方法:
private void DoStuff(StuffToDo stuff)
{
DoStuff(stuff.ToString());
}
private void DoStuff(string stuff)
switch (stuff)
{
case "Swim":
break;
case "Bike":
break;
case "Run":
break;
default:
throw new ArgumentOutOfRangeException("stuff");
}
}
然后,您可以实际测试您在默认语句中涵盖的情况。
答案 5 :(得分:1)
在单元测试用例中测试私有方法的一种方法是使用反射,但我觉得在大多数情况下可能会过度杀死,可以用其他方式进行测试。 在你的代码中,为了测试switch case,你可以做的是,如果你公开的类方法将StuffToDo作为参数,那么你需要编写多个测试用例,每个测试用例都为StuffToDo传递一组不同的值。这样,当你的switch语句被执行时,你可以使用你的公共方法本身验证行为,但在这种情况下,我假设你从你的公共方法获得输出。
看看你的代码我得到的另一种感觉是,你的公共方法没有采取任何输入,也没有给出任何结果,但做了一些看起来不正确的修改。它看起来像是在默默地改变可能混淆的东西。
尝试使用更具体的方法,清楚地说明它所采取的输入以及如何修改它。
答案 6 :(得分:1)
您提到的“问题”是切换...案件的内在因素,因此您可以做的最好的事情就是依靠不同的选择。我个人不喜欢过多的开关......因为它缺乏灵活性。因此,这个答案旨在提供一种替代switch语句,能够提供您正在寻找的零不确定性。
在这种情况下,我通常依赖于从函数开头填充的数组中获取的一组条件,这些条件将自动过滤任何未计入的输入。例如:
private void DoStuffMyWay(StuffToDo stuff)
{
StuffToDo[] accountedStuff = new StuffToDo[] { StuffToDo.Bike, StuffToDo.Run, StuffToDo.Swim };
if (accountedStuff.Contains(stuff))
{
if (stuff == accountedStuff[0])
{
//do bike stuff
}
else if (stuff == accountedStuff[1])
{
//do run stuff
}
else if (stuff == accountedStuff[2])
{
//do swim stuff
}
}
}
这当然不是太优雅,但我想我不是一个优雅的程序员。
至于您可能更喜欢采用不同类型的方法解决问题,这里有另一种选择;效率较低,但看起来更好:
private void DoStuffWithStyle(StuffToDo stuff)
{
Dictionary<StuffToDo, Action> accountedStuff = new Dictionary<StuffToDo, Action>();
accountedStuff.Add(StuffToDo.Bike, actionBike);
accountedStuff.Add(StuffToDo.Run, actionRun);
accountedStuff.Add(StuffToDo.Swim, actionSwim);
if (accountedStuff.ContainsKey(stuff))
{
accountedStuff[stuff].Invoke();
}
}
private void actionBike()
{
//do bike stuff
}
private void actionRun()
{
//do run stuff
}
private void actionSwim()
{
//do swim stuff
}
使用第二个选项,您甚至可以将字典创建为全局变量,并将其与枚举一起填充。
答案 7 :(得分:1)
编写用于测试超出范围值并期望异常的测试。
答案 8 :(得分:1)
它是无法访问的代码,因为交换机案例类型是Enum,并且您已编码所有可能的案例。永远不会出现“默认”的情况。
你可以添加'Unknown'或一些额外的enum值,这些值不应该有相应的case语句,然后这个警告就会消失。
private enum StuffToDo
{
Swim = 0,
Bike,
Run,
// just for code coverage
// will never reach here
Unknown
}
代码覆盖率没有深入评估每种可能性,因此不是测试它的问题。它根据预编码的预期行为匹配代码模式
Code Coverage认为这样,你已经添加了现有枚举的所有可能性,没有办法默认调用。这就是他们的规则设置方式,你无法改变他们的规则。
答案 9 :(得分:0)
My Resharper还“抱怨”无法访问的代码,我在编程时不喜欢这些代码。叫我肛门,但我喜欢从我看到它的那一刻起摆脱死代码。
对于你的具体情况,我会选择其中一种情况作为默认情况,并把它放到这样的东西:
private void DoStuff(StuffToDo stuff)
{
switch (stuff)
{
case StuffToDo.Swim:
break;
case StuffToDo.Bike:
break;
case StuffToDo.Run:
default:
break;
}
}
由于您努力100%测试覆盖率,理想情况下,当您添加应该区别对待的枚举条目时,这应该会中断。
如果你真的想确定,你仍然可以解开它:
private void DoStuff(StuffToDo stuff)
{
switch (stuff)
{
case StuffToDo.Swim:
break;
case StuffToDo.Bike:
break;
case StuffToDo.Run:
default:
if (stuff != StuffToDo.Run) throw new ArgumentOutOfRangeException("stuff");
break;
}
}