5

我在一个类中有一个公共方法,该方法在该类中内部调用了一个特定的私有方法。它看起来像这样:

public class MyClass : IMyClassInterface
{
    public List<int> MyMethod(int a, int b)
    {
        MyPrivateMethod(a, b, ref varList, ref someVal);
    }
    private void MyPrivateMethod(int a, int b, ref List<int> varList, ref double someval)
    {
    }
}

现在,我基本上想用 NUnit 测试这个公共方法。我正在使用 NMock 2.0 进行模拟。我该怎么做?因为,它在内部调用了这个我不想公开的私有方法。或者,如果我将私有方法改为受保护,是否有办法做到这一点?

4

3 回答 3

6

现在,我基本上想测试这个公共方法(...)

这很棒。这是你应该做的。暂时忘记内部细节。从公共方法的角度来看,这两个片段之间有什么区别吗?

// Your current implementation
public void MyMethod(int a, int b)
{
    MyPrivateMethod(a, b);
}
private void MyPrivateMethod(int a, int b)
{
    var c = a + b;
    // some more code
}

// Private method inlined
public void MyMethod(int a, int b)
{
    var c = a + b;
    // some more code
}

无论谁打电话(公众)MyMethod都不会注意到这两者之间的任何区别。最终结果是一样的。是否有私有方法调用并不重要,因为就公共 API 而言,它是无关紧要的。你可以内联所说的私有方法,让它永远消失,从公共消费者的角度来看,没有任何改变。最终结果是唯一重要的事情。您测试代码使用者可观察到的最终结果。不是一些内部的胡言乱语。

重要的认识是:

正确设计的 SOLID 代码永远不会让您处于需要您进行私人模拟的位置。问题的根源?糟糕的设计。

资料来源:如何模拟私有方法 - 解决方案

是的。可悲但真实,你的设计不是那么好。根据您是否要更改它,您可以采取几种方法:

  • 不要试图模拟私人细节,专注于公共 API(对设计问题没有帮助)
  • 将私有方法提取到类,引入依赖项(长期解决方案,改进设计并使代码易于测试)
  • 使私有方法受到保护,按照其他答案中的建议在测试中覆盖(对设计问题没有帮助,可能不会产生有价值的测试)

无论你选择哪个,我都留给你。但是,我将再强调一次——模拟私有方法不是单元测试、库或工具问题——它是一个设计问题,最好这样解决。


附带说明一下,(如果可以的话)不要使用 NMock2。这是一个自 2009 年以来最后一次更改的图书馆。就像拥有一辆 30 年的旧汽车,最后一次维修是在 15 年前。现在有更好的(FakeItEasy、Moq、NSubstitute)。

于 2015-07-09T06:58:28.197 回答
5

是的,“技巧”是使用受保护而不是私有,然后继承该类并在执行受保护方法的新类上运行测试。这是使棕地和遗留代码可测试的一种非常常见的方法。

    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void TestMethod1()
        {
            MyClassTestWrapped t = new MyClassTestWrapped();
            Assert.IsTrue(t.MyPrivateMethod(...));
            Assert.IsTrue(t.MyMethod(...));

            MockFactory _factory = new MockFactory();
            Mock<MyClassTestWrapped> mock;

            mock = _factory.CreateMock<MyClass>();
            mock.Expects.One.MethodWith(d => d.MyPrivateMethod());  // do the nmock magic here


        }
    }

    public class MyClass : IMyClassInterface
    {
        public List<int> MyMethod(int a, int b)
        {
            MyPrivateMethod(a, b, ref varList, ref someVal);
        }
// here change to protected
        protected void MyPrivateMethod(int a, int b, ref List<int> varList, ref double someval)
        {
        }
    }

    public interface IMyClassInterface
    {

    }

    public class MyClassTestWrapped : MyClass
    {
        public List<int> MyMethod(int a, int b)
        {
            base.MyMethod(a, b);
        }

        public List<int> MyPrivateMethod(int a, int b,ref List<int> varList, ref double someval)
        {
            base.MyPrivateMethod(a, b, ref varList, ref someval);
        }

    }
于 2015-07-09T04:57:28.863 回答
1

虽然目前您必须重构代码以丢失私有修饰符(包装器等),但您可以使用 Typemock Isolator 等工具轻松完成。

我在您的示例中添加了一些代码来编写测试:

public class MyClass 
{
    public List<int> MyMethod(int a, int b)
    {
        List<int> varList = new List<int>();
        double someVal = 0;

        MyPrivateMethod(a, b, ref varList, ref someVal);

        return varList;
    }

    private void MyPrivateMethod(int a, int b, ref List<int> varList, ref double someval)
    {
    }
}

使用这种直接的方法,您只需伪造代码中的私有方法(生产中没有更改),即使它是 ref 参数:

[Test]
public void TestMethod1()
{
    //Arrange
    var myClass = new MyClass();
    var expectedVarList = new List<int> {1,2,3};

    Isolate.NonPublic.WhenCalled(myClass, "MyPrivateMethod")
        .AssignRefOut(expectedVarList, 0.0)
        .IgnoreCall();

    //Act
    var resultVarList = myClass.MyMethod(0, 0);

    //Assert
    CollectionAssert.AreEqual(expectedVarList, resultVarList);

}
于 2015-07-09T07:52:49.420 回答