模拟一个方法进行测试

23

尝试Mock一个在另一个方法内被调用的方法。

// code part
public virtual bool hello(string name, int age)
{
    string lastName = GetLastName();
}

public virtual string GetLastName() 
{
    return "xxx"; 
}

// unit test part
Mock<Program> p = new Mock<Program>();
p.Setup(x => x.GetLastName()).Returns("qqq");

我希望GetLastName方法始终返回"qqq"。


1
你尝试过什么,遇到了什么问题?看起来是正确的。 - Kritner
1
当我设置断点时,我的姓氏的值仍然等于xxx。同时单元测试也失败了。 - codeislife
如果您只想模拟在扩展的xmethod类中调用的base.xmethod(),该怎么办? - Sarfraj Ansari
2个回答

29

假设这些是完整的方法实现,那么这应该可以运行。

public class MyProgram
{

    public bool hello(string name, int age)
    {
        string lastName = GetLastName();

        return string.Format("hello {0}", lastName);
    }

    public virtual string GetLastName() 
    {
        return "xxx"; 
    }
}

public class MyProgramTests
{

    [TestMethod]
    public void MyTest()
    {

        string stringToReturn = "qqq";
        Mock<MyProgram> name = new Mock<MyProgram>();
        name.CallBase = true;
        name.Setup(x => x.GetLastName()).Returns(stringToReturn );

        var results = name.Object.hello(It.IsAny<string>(), It.IsAny<int>());

        string expected = string.Format("hello {0}", results);

        Assert.AreEqual(expected, results);
    }
}

I'm still not quite following your comment:
什么是Mock的参数???应该是什么?抱歉,我不太理解语法。澄清一下,mock的意思是当我在代码中设置断点时,这些断点应该跳过我正在模拟的方法。我说的对吗?
Mock允许您模拟类型T-T是一个通用指示符,也意味着几乎任何类。在传统方式中,您将模拟一个接口,而不是实际类,但在上面的示例中,我们正在模拟一个类。对于发布的样本单元测试,单元测试的目的是测试hello(string, int)的实现。我们知道hello(string, int)依赖于该类中称为GetLastName()的另一种方法。GetLastName()的实现虽然很重要,但对于单元测试hello(string, int)的范围并不重要。因此,我们模拟调用及其返回-以测试hello(string, int)的功能,而无需担心其依赖项的实现。
我已将上述内容用实际的类名进行了标识,以便更清楚地表明我们正在模拟类 MyProgram 并提供新的实现(模拟)GetLastName()

谢谢您的回答。如果我想测试调用另一个方法的方法呢?例如,如果方法hello调用另一个方法怎么办?

同样的原则适用于您构建单元测试时(假设它们是单元测试,而不是集成测试或其他测试),您总是要专注于测试一个公共方法。单元测试和集成测试有什么区别?

public class Foo
{

    public string Bar()
    {
        return string.Format("{0}Bar", Baz(5));;
    }

    public virtual string Baz(int someNumber)
    {
        return string.Format("{0}Baz", DoStuff(someNumber).ToString());
    }

    public virtual int DoStuff(int someNumber)
    {
        return someNumber+1;
    }

}

如果我们正在对Bar()进行单元测试,我们并不关心Baz(int)甚至更糟的是DoStuff(int)的实现。请注意我们不关心实现细节,我们确实关心它们返回的值。从Bar()的角度来看,唯一重要的事情是Baz(int)返回一个字符串。那个字符串是什么?这对于Bar()的单元测试并不重要。 Bar()的示例测试:
[TestMethod]
public void Bar_ReturnsBazValueWithBarAppended
{
    // Arrange
    string testBazReturn = "test";
    Mock<Foo> mock = new Mock<Foo>();
    mock.CallBase = true;
    mock
        .Setup(s => s.Baz(It.IsAny<int>())
        .Returns(testBazReturn);

    // Act
    var results = mock.Object.Bar();

    // Assert
    Assert.AreEqual(string.Format("{0}{1}", testBazReturn, "Bar"), results);
    mock.Verify(v => v.Baz(It.IsAny<int>())); // Verifies that Baz was called
}

在上面的例子中,我们实际实现的和并不重要,因为我们忽略了和的实际实现,甚至没有起作用。现在,如果我们要测试,我们只需遵循相同的思路:
[TestMethod]
public void Baz_ReturnsDoStuffValueWithBazAppended
{
    // Arrange
    int testDoStuffReturn = 1;
    Mock<Foo> mock = new Mock<Foo>();
    mock.CallBase = true;
    mock
        .Setup(s => s.DoStuff(It.IsAny<int>())
        .Returns(testDoStuffReturn);

    // Act
    var results = mock.Object.Baz(5);

    // Assert
    Assert.AreEqual(string.Format("{0}{1}", results, "Baz"), results); // Validates the result
    mock.Verify(v => v.DoStuff(It.IsAny<int>())); // Verifies that DoStuff was called
}

在上面的例子中,现在我们正在对Baz(int)进行单元测试,我们不关心Bar(),而且我们只关心DoStuff(int)返回一个值(但不关心它如何到达该值)。最后是DoStuff(int)
[TestMethod]
public void DoStuff_ReturnsParameterPlusOne()
{
    // Arrange
    Foo foo = new Foo();
    int passed = 1;
    int expected = passed + 1;

    // Act
    var results = foo.DoStuff(passed);

    // Assert
    Assert.AreEqual(expected, results);
}

我应该在程序中使用基类还是扩展类,这对程序有影响吗?(Mock中的参数) - codeislife
我不明白你的问题,请在问题中详细说明并提供示例。 - Kritner
Mock<??>的参数真正意味着什么???应该是什么?对不起,我不太理解这个语法。澄清一下,mock的意思是当我在代码中设置断点时,断点应该跳过我正在mock的方法。我说得对吗? - codeislife
谢谢你的回答。如果我想测试一个调用另一个方法的方法,该怎么办?例如,如果方法hello调用了另一个方法呢? - codeislife
1
仅仅为了模拟一个方法而使用虚拟方法,这样做会采用危险的代码实践。在这里使用接口会更加安全。 - Alex McLean
显示剩余2条评论

0

还有一个.CallBase()扩展方法,它允许您控制是否为每个单独的方法调用原始方法,这可能更有用:

name.Setup(x => x.hello()).CallBase();

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接