C# 单元测试-让单元测试调用其他单元测试是一种不好的形式吗

C# 单元测试-让单元测试调用其他单元测试是一种不好的形式吗,c#,visual-studio,unit-testing,C#,Visual Studio,Unit Testing,我有一个名为TestMakeAValidCall()的单元测试。它测试我的手机应用程序是否拨打了有效的电话 我将要编写另一个名为TestShowCallMessage()的测试,它需要对测试进行有效调用。在那个测试中只调用TestMakeAValidCall()是一种糟糕的形式吗 这是我的TestMakeAValidCall()test,仅供参考 [TestMethod] public void TestMakeAValidCall() { //Arrang

我有一个名为
TestMakeAValidCall()
的单元测试。它测试我的手机应用程序是否拨打了有效的电话

我将要编写另一个名为
TestShowCallMessage()
的测试,它需要对测试进行有效调用。在那个测试中只调用
TestMakeAValidCall()
是一种糟糕的形式吗

这是我的
TestMakeAValidCall()
test,仅供参考

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

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

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

IMHO,您应该执行以下操作之一:

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

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

是-单元测试应该是独立的,并且应该只测试一件事情(或者至少测试少量密切相关的事情)。另一方面,测试方法中对data.Expect和phone.Expect的调用创建的是期望,而不是存根调用,如果您重构…

以提供一个相反的观点,这会使测试变得脆弱:

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

当然,只有当测试框架知道这些依赖项,从而在依赖项失败时停止运行依赖项测试时,这才有意义。更好的是,这样的框架可以通过一个又一个测试,这样就可以在不断增长和扩展的夹具上构建,而不是在每次测试中从头开始重新构建夹具。当然,缓存是为了在多个测试依赖于同一个示例时不会引入任何副作用

我们把这个想法付诸实践。目前还没有C#端口,尽管有用于和的端口。。。这个


PS:.

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

“在这种情况下,有没有人能解释一下重构是什么样子的?”Philip Bergström 2015年11月28日15:33”

我目前正在做类似的事情,这就是我想到的:

注意ProcessorType和BuildProcessors都调用TestLevel

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

它使用XUnit,并且应该使用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);
}
私有静态无效测试级别(ArgProcessor incomingProcessor)
{
Action currentLevelIteration=null;
currentLevelIteration=(currentProcessor,currentLevel)=>
{
currentProcessor.CurrentLevel.应与(CurrentLevel)等效;
ProcessorLevel nextProcessor=currentProcessor.currentProcessor;
如果(下一个处理器!=null)
currentLevel迭代(下一个处理器,currentLevel+1);
};
currentLevelIteration(输入处理器,0);
}
[理论]
[InlineData(类型(构建),“构建”)]
public void ProcessorType(类型ProcessorType,参数字符串[]args)
{
ArgProcessor newCLI=new OriWeb_CLI.ArgProcessor(args);
IncomingArgumentsTests.TestLevels(newCLI);
newCLI.CurrentProcessor.ShouldBeOfType(ProcessorType);
}
[理论]
[InlineData(typeof(Build.TypeScript),“TypeScript”)]
[InlineData(typeof(Build.CSharp),“CSharp”)]
public void BuildProcessors(类型ProcessorType,参数字符串[]args)
{
List newArgs=newlist{“Build”};
foreach(args中的字符串arg)newArgs.Add(arg);
ArgProcessor newCLI=new OriWeb_CLI.ArgProcessor(newArgs.ToArray());
IncomingArgumentsTests.TestLevels(newCLI);
newCLI.CurrentProcessor.CurrentProcessor.ShouldBeOfType(ProcessorType);
}

感谢所有伟大的答案。我将重复的代码重构为一个单独的调用。我以最多的票数选择了答案。感谢对我的术语的更正。我已经更新了源代码中的注释。有趣的是,如果做得正确,可以大大减少重复。不要用test测试其他测试。有人可以吗ellaborate在本例中如何重构?@Nakata-在不知道依赖项是如何构造的情况下,很难显示此示例的确切代码,但本要点显示了我在测试上下文中放置设置代码并在多个测试中从那里调用它的模式。谢谢,满足设置调用是一种好的约定吗嵌套类中的hod?@Nakata-使用内部类限制对包含类的公共方法的访问。我这样做是为了限制测试类之间的耦合,消除过度设计它们以满足不相关测试类的需要的诱惑。如果您确实拥有可重用的代码,请将其移动到已退出的基本测试上下文将测试类放在一边,并从该基类扩展内部测试上下文。