【问题标题】:Unit Testing - Is it bad form to have unit test calling other unit tests单元测试 - 让单元测试调用其他单元测试是不好的形式
【发布时间】:2010-11-25 00:43:18
【问题描述】:

我有一个名为TestMakeAValidCall() 的单元测试。它会测试我的手机应用是否能打出有效的电话。

我即将编写另一个名为TestShowCallMessage() 的测试,它需要为测试进行有效调用。在那个测试中只调用TestMakeAValidCall() 是不是很糟糕?

作为参考,这是我的TestMakeAValidCall() 测试。

    [TestMethod]
    public void TestMakeAValidCall()
    {
       //Arrange
        phone.InCall = false;
        phone.CurrentNumber = "";
        // Stub the call to the database
        data.Expect(x => x.GetWhiteListData()).
            Return(FillTestObjects.GetSingleEntryWhiteList());
        // Get some bogus data
        string phoneNumber = FillTestObjects.GetSingleEntryWhiteList().
            First().PhoneNumber;
        // Stub th call to MakeCall() so that it looks as if a call was made.
        phone.Expect(x => x.MakeCall(phoneNumber)).
            WhenCalled(invocation =>
                       {
                           phone.CurrentNumber = phoneNumber;
                           phone.InCall = true;
                       });

       //Act
        // Select the phone number
        deviceControlForm.SelectedNumber = phoneNumber;
        // Press the call button to make a call.
        deviceMediator.CallButtonPressed();

       //Assert
        Assert.IsTrue(phone.InCall);
        Assert.IsTrue(phone.CurrentNumber == phoneNumber);
    }

【问题讨论】:

  • 感谢所有出色的答案。我将重复的代码重构为单独的调用。我选择了得票最多的答案。

标签: c# visual-studio unit-testing


【解决方案1】:

将设置重构为另一个方法并从两个测试中调用该方法。测试不应调用其他测试。

【讨论】:

  • 不要用 test 测试另一个测试
  • 有人可以详细说明在这种情况下重构的样子吗?
  • @Nakata - 很难在不知道依赖项的结构的情况下显示此示例的确切代码,但这个要点显示了我用来将设置代码放在测试上下文中并从那里调用它的模式在多次测试中。 gist.github.com/tvanfosson/1ca6dd3bb0b796de65b0
  • 谢谢,将 SetupCall 方法放在嵌套类中是一个好的约定吗?
  • @Nakata - 使用内部类将对其公共方法的访问限制为包含类。我这样做是为了限制测试类之间的耦合,消除过度设计它们以满足不相关测试类的需求的诱惑。如果您确实有可以重用的代码,请将其移至测试类外部的基本测试上下文,并从该基类扩展内部测试上下文。
【解决方案2】:

恕我直言,您应该执行以下操作之一:

  • 创建一个返回有效调用的方法,并将其分别用于两个测试(不是一个调用另一个)
  • 模拟 ShowCallMessageTest 的有效调用

【讨论】:

    【解决方案3】:

    提供一个反驳点:

    我坚信设计良好的单元测试应该相互依赖!

    当然,这只有在测试框架知道这些依赖关系时才有意义,这样它就可以在依赖关系失败时停止运行依赖测试。更好的是,这样的框架可以将夹具从一个测试传递到另一个测试,这样可以构建一个不断增长和扩展的夹具,而不是为每个单独的测试从头开始重建它。当然,缓存是为了避免在多个测试依赖于同一个示例时引入副作用。

    我们在JExample extension for JUnit 中实现了这个想法。目前还没有 C# 端口,尽管有 RubySmalltalk 和 ...most recent release of PHPUnit picked up both our ideas: dependencies and fixture reuse 的端口。

    PS:folks are also using it for Groovy.

    【讨论】:

    • 有趣的一点,如果做得正确,可以大大减少重复。
    【解决方案4】:

    我认为这是个坏主意。您希望您的单元测试只测试一件事和一件事。不要通过其他测试创建调用,而是模拟调用并将其作为参数传递。

    【讨论】:

      【解决方案5】:

      单元测试应根据定义测试代码的一个单元/功能。让它调用其他单元测试使其测试多个单元。我将其分解为单独的测试。

      【讨论】:

        【解决方案6】:

        是的 - 单元测试应该是分开的,并且应该只测试一件事(或至少一小部分密切相关的事情)。顺便说一句,在您的测试方法中对 data.Expect 和 phone.Expect 的调用正在创建期望而不是存根调用,如果您重构,这会使您的测试变得脆弱......

        【讨论】:

        • 感谢您对我的术语的更正。我已经更新了我的源代码中的 cmets。
        【解决方案7】:

        单元与模块....我们还认为测试也应该依赖于可重用的方法,并且应该在 api 级别测试类的集成。许多只是测试一个类,但许多错误都在类级别之间的集成上。我们还使用 verifydesign 来保证 api 不依赖于实现。这允许您重构整个组件/模块而无需进行任何测试(我们实际上经历过一次,效果很好)。当然,任何架构更改都会迫使您重构测试,但至少模块中的设计更改不会导致测试重构工作(除非您隐含地更改 api 的行为,例如触发比以前更多的事件,但是“会”无论如何都会是一个 api 更改)。

        【讨论】:

          【解决方案8】:

          “有人能详细说明在这种情况下重构的样子吗?——Philip Bergström 2015 年 11 月 28 日 15:33”

          我目前正在做这样的事情,这就是我想出的:

          注意 ProcessorType 和 BuildProcessors 都调用 TestLevels

          除此之外的实际内容并不重要

          它使用 XUnit 和 Shouldly NuGet 包

          private static void TestLevels(ArgProcessor incomingProcessor)
          {
              Action<ProcessorLevel, int> currentLevelIteration = null;
              currentLevelIteration = (currentProcessor, currentLevel) =>
              {
                  currentProcessor.CurrentLevel.ShouldBeEquivalentTo(currentLevel);
                  ProcessorLevel nextProcessor = currentProcessor.CurrentProcessor;
                  if (nextProcessor != null)
                      currentLevelIteration(nextProcessor, currentLevel + 1);
              };
          
              currentLevelIteration(incomingProcessor, 0);
          }
          
          [Theory]
          [InlineData(typeof(Build), "Build")]
          public void ProcessorType(Type ProcessorType, params string[] args)
          {
              ArgProcessor newCLI = new OriWeb_CLI.ArgProcessor(args);
          
              IncomingArgumentsTests.TestLevels(newCLI);
          
              newCLI.CurrentProcessor.ShouldBeOfType(ProcessorType);
          }
          
          [Theory]
          [InlineData(typeof(Build.TypeScript), "TypeScript")]
          [InlineData(typeof(Build.CSharp), "CSharp")]
          public void BuildProcessors(Type ProcessorType, params string[] args)
          {
              List<string> newArgs = new List<string> {"Build"};
              foreach(string arg in args) newArgs.Add(arg);
          
              ArgProcessor newCLI = new OriWeb_CLI.ArgProcessor(newArgs.ToArray());
          
              IncomingArgumentsTests.TestLevels(newCLI);
          
              newCLI.CurrentProcessor.CurrentProcessor.ShouldBeOfType(ProcessorType);
          }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2012-01-08
            相关资源
            最近更新 更多