在TDD(测试驱动开发)中使用 Mocks 的最佳实践(MOQ)

4

我是新手,对使用模拟测试工具并不熟悉。但是,它的主要目的是什么?我将使用Moq来测试我的应用程序(和NUnit)。

例如,我有这样的代码:

我的网页代码后台:

public partial class MyWebpage
{
    protected string GetTitle(string myVar)
    {
        return dataLayer.GetTitle(myVar);
    }
}

我的数据访问层:
public class DataLayer
{
    public string GetTitle(string myVar)
    {
        // Create the query we want
        string query = "SELECT title FROM MyTable " + 
            "WHERE var = @myVar";

        //ENTER PARAMETERS IN HERE

        // Now return the result to the view
        return this.dataProvider.ExecuteMySelectQuery(
            dr =>
            {
               //DELEGATE DATA READER PASSED IN AND TITLE GETS RETURNED
            },
            query,
            parameters);
    }
}

我的数据提供者直接与数据库进行交互和通信:

 public class DataProvider
{
     public T ExecuteMySelectQuery<T>(Func<IDataReader, T> getMyResult, string selectQuery, Dictionary parameters)
    {
          //RUNS AND RETURNS THE QUERY
     }
}

什么是测试所有这些的最佳方式?
2个回答

3

如果您想单独测试各层,需要为您的DataProvider和DataLayer类创建接口,暴露您想要模拟的方法。然后,您可以使用一个mocking框架——NSubstitute非常好用,只需编写较少的代码即可创建mocks——来模拟对依赖类的调用,从而使您能够测试该特定单元内的代码。

public interface IDataProvider
{
     T ExecuteMySelectQuery<T>(Func<IDataReader, T> getMyResult, string selectQuery, Dictionary parameters);
}

public interface IDataLayer
{
    string GetTitle(string myVar);
}

public class DataLayer 
{
    private IDataProvider dataProvider;

    public DataLayer(IDataProvider dataProvider)
    {
        this.dataProvider = dataProvider;
    }
}

然后,在您的测试代码中,您创建模拟对象而不是真实对象,并在实例化测试对象时将这些对象传递给构造函数。要测试DataLayer:

[Test]
public void WhenRetievingTitleFromDataStore_ThenDataLayerReturnsTitle() 
{
    var title = "Title";
    var dataProviderMock = new Mock<IDataProvider>(MockBehavior.Strict);
    dataProviderMock.Setup(x => x.ExecuteMySelectQuery(<parameters>)).Returns(title);
    var dataLayer = new DataLayer(dataProviderMock.Object);
    Assert.That(dataLayer.GetTitle(It.IsAny<string>(), Is.EqualTo(title));
}

是的,我为它们编写了接口,就像你在上面所做的那样(只是在帖子中减少了代码)。你会如何测试你上面的代码? - cdub
使用MOQ,您可以像这样创建IDataProvider接口的模拟:Mock<IDataProvider> dataProviderMock = new Mock<IDataProvider>(MockBehavior.Strict); 然后将其传递给数据层,如下所示:var dataLayer = new DataLayer(dataProviderMock.Object); - levelnis
1
这是一个不会影响GetTitle方法外部任何内容的输入参数吗?如果您不关心它的值,可以使用Moq的It.IsAny<string>() - 将更新测试。如果您需要将其传递到ExecuteMySelectQuery方法中,则可以在测试中为该值创建一个常量,并使用该常量表示传递给ExecuteMySelectQuery的参数。然后,您将该特定常量值传递给GetTitle。如果在调用ExecuteMySelectQuery时该字符串不同,则模拟将失败。 - levelnis
1
@jgauffin 这个测试验证了DataLayer与其DataProvider正确协作,更确切地说,它能够处理来自DataProvider的"Title"返回值。 - guillaume31
我在谈论答案中的测试。如果您将模拟替换为真正的 IDataProvider,则会得到一个测试,可以保证代码有效(只要没有人在测试后搞乱数据库)。模拟 IDataProvider 无法做到这一点。 - jgauffin
显示剩余12条评论

2

唯一可能出现问题的就是数据库调用(查询或返回结果类型不正确)。这无法模拟。您需要进行集成测试而不是单元测试。

通常情况下,只有在测试代码中的逻辑时才会进行模拟。例如,您应该测试数据映射器(this.dataProvider.ExecuteMySelectQuery)是否按定义工作。但这超出了所讨论的代码范围。

更新

因此,您拥有以下类:

public class DataLayer
{
    public string GetTitle(string myVar)
    {
        // Create the query we want
        string query = "SELECT title FROM MyTable " + 
            "WHERE var = @myVar";

        //ENTER PARAMETERS IN HERE

        // Now return the result to the view
        return this.dataProvider.ExecuteMySelectQuery(
            dr =>
            {
               //DELEGATE DATA READER PASSED IN AND TITLE GETS RETURNED
            },
            query,
            parameters);
    }
}

public class DataProvider
{
     public T ExecuteMySelectQuery<T>(Func<IDataReader, T> getMyResult, string selectQuery, Dictionary parameters)
    {
          //RUNS AND RETURNS THE QUERY
     }
}

如果我们检查ExecuteMySelectQuery,我们可以看到DataLayer类依赖于数据库返回的类型,因为DataProvider只是简化了查询执行。可以说它是在ADO.NET之上的一个附加组件。
这也意味着您不能保证DataLayer返回承诺的内容而不涉及数据库。例如,假设数据库中的table有一个名为title的列,但有人使用了int数据类型。
可能出现以下问题:
  • 查询不正确
  • 数据库架构不正确(错误的列名、数据类型等)
  • 映射
这些错误都无法通过模拟进行检测或测试。
另一方面,如果您在另一个类中使用DataLayer类,则当然可以对其进行模拟。因为DataLayer类本身是一个完整的抽象。这意味着调用该类的人不必知道其下面的任何内容。因此,模拟是完全可以的。

不行。我看不出意义。这段代码什么都没做。唯一可能出错的就是我说的那个。 - jgauffin
这是一种方法。其他人会测试对象与其依赖之间的每个交互。我想这归结于你认为什么是“逻辑”,因此值得测试。 - guillaume31
请看我对你在另一个答案上的评论。 - jgauffin

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