单元测试 - 让单元测试调用其他单元测试是不好的形式

时间:2009-09-02 16:51:49

标签: c# visual-studio unit-testing

我有一个名为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);
    }

8 个答案:

答案 0 :(得分:52)

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

答案 1 :(得分:11)

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

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

答案 2 :(得分:6)

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

答案 3 :(得分:6)

提供一个反点:

  

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

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

我们在JExample extension for JUnit中实施了这个想法。虽然有RubySmalltalk以及most recent release of PHPUnit picked up both our ideas: dependencies and fixture reuse的端口,但还没有C#端口。

PS:folks are also using it for Groovy

答案 4 :(得分:4)

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

答案 5 :(得分:2)

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

答案 6 :(得分:1)

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

答案 7 :(得分:0)

“有人能对这种情况下的重构情况有所了解吗?– PhilipBergström,2015年11月28日,15:33”

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

请注意,ProcessorType和BuildProcessor都调用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);
}