switch 语句的测试用例 C#

Test case for a switch statement C#

我是 C# 和单元测试的新手。我需要为 switch 语句编写单元测试,我不得不承认我已经浏览了许多页面试图找到一些东西。有没有人可以给我任何提示如何创建它?请 我的代码如下:

public static Message create(String body)
{
    Message result = null;
    switch (body.ToArray()[0])
    {
        case 'S':
            //checking the regex for a sms message
            Match matchS = Regex.Match(body, @"[S][0-9]{9}\+[0-9]{12}");
            if (matchS.Success)
            {
                MessageBox.Show("This is SMS message");
                //if the regex will match a sms_message class will be started
                result = new Sms_Message(body.Substring(1));
            }
            //if the regex doesn't match the message should be displayed
            else throw new Exception("I don't like content!!!!!!!!!!");       
            break;
        case 'T':
            //checking the regex for a tweet message
            Match matchT = Regex.Match(body, @"[T][0-9]{9}");                     
            if (matchT.Success)
            {
                MessageBox.Show("This is the Tweet message");
                //if the regex match the message should be displayed and the class Tweet will be started         
                result = new Tweet(body.Substring(1));
            }
            break;
        case 'E':
            //checking the regex for a email message
            Match matchE = Regex.Match(body, @"[E][0-9]{9}"); 
            if (matchE.Success)
            {
                //checking the content of the message by using function 'BodyIsSir'
                if (BodyIsSir(body))
                    //if function return true the SIREmail class will be started                                               
                    result = new SIREmail(body.Substring(1));                       
                else
                    //if function return false the StandardEmail class will be started
                    result = new StandardEmail(body.Substring(1));                  
            }
            //when regex will not match the text message will be displayed
            else throw new Exception("I don't like the content");                   
            break;
        default:
            //when the content of the email message will not match
            throw new Exception("I dont like first letter!");                         
    }
    return result;
}

private static bool BodyIsSir(string body)
{
    //checking the body of email message if this contain regex for checking the sort code
    Match matchSIR = Regex.Match(body, @"[0-9]{2}[-][0-9]{2}[-][0-9]");              
    if (matchSIR.Success)
        return true;                                                                
    else
        return false;                                                                   
}

在我看来,您应该为每个案例创建多个测试。 您可以根据不同的情况使用不同的方法制作辅助界面和 class(我称之为 IController)。

     IController controller;
     case 'S':
         controller.CaseS ();
        break;
      case 'T':
          controller.CaseT(body);
          break;
      case'E':
            controller.CaseE(body);
          break;
      default:
          throw new Exception("I dont like first letter!");   

并进行小型单元测试,如:如果正文以 'S' 开头,它将调用 controller.CaseS () 等等,使用您的 IController 模拟对象。 然后进行测试以涵盖 IController.

的非模拟实现行为

或者用不同的输入做几个测试:如果 body 等于 "S..." 如果应该 return 这个结果。如果 body 等于 'P..' 则抛出异常

此外,我建议避免直接使用 MessageBox.Show 并使用 Show 方法创建 IMEssageBoxHelper 并使用它代替 MessageBox.Show(),因此在您的测试中您可以更改它模拟实现,因此您的测试将 运行 没有消息框。

如果您想测试可能的情况,可以在测试方法上使用 TestCaseSource 属性。这将迭代指定的源并调用每个项目的测试方法。

[TestFixture]
public class MessageTests
{
    static Case[] ValidCases = {
        new Case ("S...", typeof(Sms_Message)),
        new Case ("T...", typeof(Tweet)),
        new Case ("E...", typeof(SIREmail)),
        new Case ("E...", typeof(StandardEmail))
    };

    [Test]
    [TestCaseSource("ValidCases")]
    public void Create_ShouldCreate_WhenValidSource(Case currentCase)
    {
        // when
        Message created = TargetClass.create (currentCase.Body);

        // then
        Assert.That (created.GetType (), Is.InstanceOf (currentCase.ExpectedResultType));
    }

    public class Case
    {
        public Case(string body, Type expectedResultType)
        {
            Body = body;
            ExpectedResultType = expectedResultType;
        }

        public string Body { get; private set; }
        public Type ExpectedResultType { get; private set; }
    }
}

在测试任何方法时,您需要考虑任何客户端在调用您的方法时会遇到的行为。本质上这归结为提出一个简单的问题:

  • 对于给定的输入,此方法的预期 output/result 是多少?

通过查看您的方法中的不同 "branches",您可以计算出您需要测试的不同输入以获得您的方法的 "full coverage"。例如,在您的方法中,以 S 开头的输入有两种可能的结果:要么匹配正则表达式,要么抛出异常。类似的分析应该产生一组输入,这些输入应该涵盖通过您的方法的每条可能路径。

您必须删除方法对 MessageBox.Show() 的依赖性 - 因为任何自动化测试都将在这一步停止,需要手动干预才能从方法中实际获得任何结果。答案 中的建议是一个很好的建议 - 使用接口消除您对此的依赖。

I need to write a unit tests for a switch statement

不,你不知道,因为那不是一个单位。

单位为:

  1. public static Message create(String body)(也就是说,你能从外面看到的代码)
  2. 影响该方法行为的任何状态,因此它可能对相同的方法做不同的事情body。 (不受状态影响而仅受输入影响的方法的优点之一是它因此使测试更容易)。
  3. 它应该做什么的记录行为,无论该文档是写下来的还是只是在您的脑海中。 (它只是在你的脑海中是一件坏事,但在某些情况下,测试本身可以作为预期行为的低级文档)。

您将单元测试写到:

  1. 也许到达正确的起点(如果您在代码之前编写测试,这通常是一个好方法)。
  2. 向您自己和其他人保证代码工作正常。
  3. 捕获由于后来的更改不小心破坏了这段代码而导致的错误。 (特别是当涉及到你的问题时。如果 switch 被其他机制取代,测试不应该改变,但你不再测试 switch)。

因此,您想编写执行此操作的测试。基本方法很简单,你有一堆不同的输入,一堆不同的预期输出或异常,然后你编写一个测试来检查它。

你没说你用的是什么测试框架。我推荐XUnit,不过NUnit和MSUnit也不错

保持单元测试小,只测试一件事*,尽管可能检查这些东西的一些特性†。例如:

[Fact]
public void SMSMessage()
{
  Message msg = YourClass.create("S123456789+123456789012");
  Assert.IsType<Sms_Message>(msg);
  Assert.Equal("123456789+123456789012", msg.Body);
}

(在 NUnit 或 MSUnit 中 [Fact] 将是 [Test].IsType 将是 .IsInstanceOfType,而 .Equal 将是 .AreEqual

检查异常与检查正确使用同样重要。首选 XUnit 而不是 NUnit 或 MSUnit 的原因之一是,虽然 XUnit 和 MSUnit 有一个定义预期异常类型的 [ExpectedException] 属性,但 XUnit 有一个方法可以更好地检查引发异常的精确调用(所以测试不能通过在错误的时间抛出正确的异常来指示成功)并允许检查抛出的异常:

[Fact]
public void InvalidSMS()
{
  Exception ex = Assert.Throws<Exception>(() => YourClass.create("S12345"));
  Assert.Equal("I don't like content!!!!!!!!!!", ex.Description);
}

您还可以对大范围的数据进行测试:

public static IEnumerable<object[]> ValidSMSMessages()
{
  yield return new object[] { "123456789-123456789012" }
  yield return new object[] { "123456912-123456789012" }
  yield return new object[] { "123672389-123456789012" }
  yield return new object[] { "121233789-123456789012" }
  yield return new object[] { "123456789-123456781212" }
  yield return new object[] { "123456789-121216789012" }
  // One could probably think of better examples here based on a mixture of realistic and edge-case uses.
}

[Theory]
[MemberData("ValidSMSMessages")]
public void SMSMessages(string smsBody)
{
  Message msg = YourClass.create("S" + smsBody);
  Assert.IsType<Sms_Message>(msg);
  Assert.Equal(smsBody, msg.Body);
}

始终尝试考虑边缘情况。例如,如果 null 或一个空字符串可以传递给一个方法,那么您应该测试这样做,并且该方法要么得到正确的结果(如果这样做有效),要么得到正确的异常(如果这样做是有效的)无效的)。 (这里的一个好处是 Assert.Throws<> 当类型是 ArgumentException 或从它派生时有一个形式接受带有预期参数名称的参数)

[Fact]
public void NullBody()
{
  Assert.Throws<ArgumentNullException>("body", () => YourClass.create(null));
}

[Fact]
public void EmptyBody()
{
  Assert.Throws<ArgumentException>("body", () => YourClass.create(""));
}

请注意,您答案中的代码未通过这两项测试。欢呼!我们的测试发现了两个需要修复的错误。

(我不清楚 "T" 的输入 return null 是错误还是设计。这就是我更喜欢 [= switch 块中的 31=] 立即分配,而不是在块中分配,然后在末尾分配 return ;如果采用这种方法,如果需要,您将不得不明确地 return null ,或者一个编译器错误。所以对于阅读代码的人来说,returning null 是正确的,或者如果不正确,它会被修复。

我们无法通过单元测试轻易发现的是设计缺陷。在有问题的代码中存在以下设计缺陷:

  1. 使用不符合 .NET 约定的名称(小写方法名称,在方法名称中使用下划线)。
  2. 抛出 Exception 而不是更具体的派生类型。
  3. 不分离业务和显示逻辑,而是从工厂方法中调用 MessageBox.Show()
  4. 调用 ToArray() 浪费时间和内存分配 char[] 只是为了访问该数组上的 [0],当用 body[0] 替换 body.ToArray()[0] 时会有同样的效果更有效。

但是:

  1. 考虑如何测试一种方法会迫使您考虑该方法应该如何工作,这会使您没有注意到的设计缺陷更加明显。
  2. 单元测试让改进更安全。假设我们只是在一段时间后才意识到 ToArray() 调用的浪费。有了单元测试,我们可以 运行 取出后再次进行单元测试。如果我们的性能改进以某种方式破坏了我们知道的东西(它不会,但如果我们能够一直正确处理所有类似的事情,我们根本不需要任何测试......)。相反,虽然所有测试 运行ning 并不能证明我们没有破坏任何东西,但它们肯定可以增加我们没有破坏的信心。

使用覆盖工具作为指导,而不是拐杖。第一次编写测试时不要查看覆盖率报告。然后当你这样做的时候,找到你的测试没有覆盖的代码路径,想想什么样的情况会涉及它们,然后在不查看的情况下为这些案例 和类似案例 添加测试覆盖范围,同时改善它。这种覆盖率确实可以指导您编写更好的测试,但如果您一直在查看覆盖率,则很容易陷入编写获得 "perfect" 覆盖率但实际测试不多的测试的陷阱。涵盖各种情况的覆盖率低的测试优于 100% 行和分支覆盖率的测试,这些测试并没有真正运用可能的排列。 (当然,也有可能一个分支没有被覆盖,因为它是不可能的,然后你可以删除死代码 and/or 用分支无法到达的断言替换一个分支)。


*阅读本文的人可能已经看到我在违反此规则的开源项目上编写的单元测试。我好多了。

†测试大量有效功能的一个示例是,如果一个方法可能应该 return 一个 IList<T> 是只读的,那么测试所有功能是合理的只读 IList<T> 在同一个测试中,因为它们是同一个定义明确的概念的所有方面。例如:

[Fact]
public void ResultIsReadonly()
{
   IList<int> list = SomeMethodThatReturnsAReadonlyList();
   Assert.True(list.IsReadonly);
   Assert.Throws<NotSupportedException>(() => list.Add(5));
   Assert.Throws<NotSupportedException>(() => list.Clear());
   Assert.Throws<NotSupportedException>(() => list.Insert(0, 1));
   Assert.Throws<NotSupportedException>(() => list.Remove(1));
   Assert.Throws<NotSupportedException>(() => list.RemoveAt(0));
   Assert.Throws<NotSupportedException>(() => list[0] = 1);
}

虽然这有七个断言,但它们都是测试结果的相同特征所必需的。相反,如果我们更关心实现只读列表的 class 而不是 return 实现的方法,我们可能应该分别考虑这些功能。