繁体   English   中英

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

我需要为switch语句编写单元测试

不,您不会,因为那不是单位。

单位是:

  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或空字符串传递给方法,则应进行测试,以确保获得正确的结果(如果这样做是有效的)或正确的异常(如果这样做是无效的)。 (在这种情况下,当类型为ArgumentException或从其派生的类型时, Assert.Throws<>具有接受带有期望参数名称的参数的形式是一种奖励)

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

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

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

(这不是我清楚的事实,是否输入"T"将返回null是一个错误或设计,这是一个原因,我宁愿return出的switch块立即,而不是在它们分配,然后return在最后;如果采用这种方法,则必须在需要return null显式地return null ,否则将发生编译器错误。因此,对于阅读代码的人来说,返回null正确是显而易见的;否则,如果错误,它将得到修复) 。

我们无法通过单元测试轻松找到设计缺陷。 在所讨论的代码中,存在以下设计缺陷:

  1. 使用不遵循.NET约定的名称(小写方法名称,方法名称中使用下划线)。
  2. 引发Exception而不是更特定于案例的派生类型。
  3. 不分离业务和显示逻辑,而是从工厂方法中调用MessageBox.Show()
  4. 调用ToArray()这浪费时间和存储器分配char[]刚刚访问[0]该阵列上,更换时body.ToArray()[0]body[0]将更加有效地具有相同的效果。

然而:

  1. 考虑如何测试方法会迫使您考虑该方法应如何工作,这会使您未注意到的设计缺陷更加明显。
  2. 单元测试使改进更加安全。 假设我们只意识到之后浪费了ToArray()时间。 有了单元测试,我们可以在取出单元测试后再次运行它。 如果我们的性能改进以某种方式破坏了我们所知道的(它不会,但是如果我们一直能做到这样的事情,我们根本就不需要任何测试……)。 相反,虽然所有运行的测试都不能证明我们没有破坏任何东西,但是它们无疑可以增加我们对没有破坏的信心。

使用覆盖率工具作为指导,而不是拐杖。 初次编写测试时,请勿查看覆盖率报告。 然后,当您这样做时,找到测试未涵盖的代码路径,考虑将涉及哪些情况,然后针对这些情况和类似情况添加测试,而无需在改善范围的情况下查看覆盖率。 这样,覆盖率确实可以指导您编写更好的测试,但是如果您一直在关注覆盖率,则很容易陷入编写测试的陷阱,而这些测试却获得了“完美”的覆盖率,而没有实际进行过多测试。 具有较差覆盖率的测试(涵盖各种情况)比具有100%的行和分支覆盖率的测试要好得多,该测试实际上并未行使可能的排列。 (当然,也有可能因为不可能而没有覆盖分支,然后可以删除无效代码和/或用断言分支无法到达的断言替换分支)。


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

†测试许多有效功能的一个示例是,如果也许某个方法应该返回只读的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);
}

尽管有七个断言,但它们都是测试结果的相同特征所必需的。 相反,如果我们比起返回一个方法更关心实现只读列表的类,那么我们可能应该分别考虑这些功能。

以我的观点,您应该为每种情况创建多个测试。 您可以根据不同的情况使用不同的方法制作帮助程序接口和类(我将其称为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!");   

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

或使用不同的输入进行多个测试:如果主体等于“ S ...”,则应返回此结果。 如果正文等于“ P ..”,则会引发异常

另外,我建议避免使用MessageBox.Show dirreclty并使用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; }
    }
}

测试任何方法时,您需要考虑任何客户端在调用您的方法时将经历的行为。 从本质上讲,这可以简单地问一个简单的问题:

  • 对于给定的输入,此方法的预期输出/结果是什么?

通过查看方法中的不同“分支”,您可以算出需要测试的不同输入,以获得方法的“完整覆盖”。 例如,在您的方法中,以S开头的输入有两种可能的结果:要么与正则表达式匹配-要么抛出异常。 类似的分析应产生一组输入,这些输入应涵盖方法中的所有可能路径。

必须删除方法上对MessageBox.Show()的依赖-因为任何自动测试都将在此步骤中停止,并且需要人工干预才能使您实际从方法中获取任何结果。 在回答的建议这里是一个很好的-使用一个接口,以消除该你的依赖。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM