对具有多个接口调用的方法进行单元测试

Unit testing a method with multiple interface calls

作为单元测试新手,我一直在读到一个测试应该针对一种方法。
那么,下面的方法DoStuff的测试方法是什么?

它是三个独立的测试,每个接口案例都有适当的 Setup 吗?
或者,它是 SetupReturns 的一项测试,更像是 Strict 行为吗?

public class MainClass
{
    IFoo myFoo;
    IBar myBar;
    IBaz myBaz;

    public MainClass(IFoo foo, IBar bar, IBaz baz) {
        this.myFoo= foo;
        this.myBar= bar;
        this.myBaz= baz;
    }

    public void DoStuff(string myParam) {
        var fooResult = myFoo.doFoo(myParam);
        myBar.doBar(fooResult);
        myBaz.doBaz();
    }
}

I've been reading that one test should be for one method

这不是真的。一个方法可能有多个单元测试。

在“DoStuff”中,除了调用myFoo和myBar之外,没有真正的behavior/logic。

在 IMO 中,单元测试没有多大价值。 非常类似于编排例程的服务调用。

我冒昧地使用 NSubstitute 而不是最小起订量,但您或许可以自己想出使用最小起订量的方法。我还将使用 AutoFixture.Xunit2,因为它很棒。

private MainClass _target;
private IFoo _foo = Substitue.For<IFoo>();
private IBar _bar = Substitute.For<IBar>();
private IBaz _baz = Substitute.For<IBaz>();

public MainClassTest()
{
    _target = new MainClass(_foo, _bar, _baz);
}

[Theory, AutoData]
public void DoStuff_Delegates_To_Foo(string myParam)
{
    _target.DoStuff(myParam);
    _foo.Received.DoFoo(Arg.Is(myParam));
}

[Theory, AutoData]
public void DoStuff_Calls_Bar_With_Result_From_Foo(string myParam, object fooResult)
{
    _foo.DoFoo(Arg.Is(myParam)).Returns(fooResult);

    _target.DoStuff(myParam);

    _bar.Received().DoBar(Arg.Is(fooResult));
}

[Theory, AutoData]
public void DoStuff_Calls_Baz_After_Foo_And_Bar(string myParam)
{
    _target.DoStuff(myParam);

    Received.InOrder(() => 
    {
        _foo.DoFoo(Arg.Any<string>());
        _bar.DoBar(Arg.Any<object>());
        _baz.DoBaz();
    }
}

我们的想法是为您想知道的关于 DoStuff 方法的每一件事都提供一个测试方法。以上只是示例,如果你愿意也可以一次测试全部写完。这里的挑战是对您测试的内容提出一个很好的描述(假设您为测试命名并且您的同事实现了它),无论如何,这里有一个命名错误的测试方法的示例,它测试所有内容。

[Theory, AutoData]
public void DoStuff_Does_What_It_Is_Supposed_To_Do(string myParam, object fooResult)
{
    _foo.DoFoo(Arg.Is(myParam)).Returns(fooResult);

    _target.DoStuff(myParam);

    Received.InOrder(() => 
    {
        _foo.DoFoo(Arg.Is(myParam));
        _bar.DoBar(Arg.Is(fooResult));
        _baz.DoBaz();
    }
}

因此,要回答您的问题,归结为命名。如果你能用一个简短的句子描述测试,那就去做吧。您可能听说过每种测试方法都应该测试一件事,而且只能测试一件事,如果您在这种情况下将 "thing" 视为可以用一个简短的句子描述的东西,它会有所帮助。