我何时在DTO模型上使用界面?

时间:2014-07-06 18:10:23

标签: .net unit-testing moq

我正在使用Moq框架进行单元测试,最近我编写了一个测试,用于检查我的一个DTO上的属性是否在方法中设置。

最初,我的方法返回了DTO ....然后是属性集。 这是我正在测试的方法。

public void UpdatePlayerProfileStatistics(DataRow playerRecord, bool useUpdatedGamesPlayed = true)
    {
        DTOProfile.Player player = this._playerDataParser.ParsePlayerProfileData(playerRecord);

        DTOProfile.Player playerInDatabase = this._playerManager.MatchPlayerInDatabase(player.Id);

        string errorMessage = String.Empty;
        if (useUpdatedGamesPlayed)
        {
            // do nothing. player.Games = player.Games; nothing changes
        }
        else
        {
            // we want to keep the Games played from the value of the player in the database.
            player.Games = playerInDatabase.Games;
        }

        if (!this.repository.UpdatePlayerProfile(player, out errorMessage))
        {
            throw new InvalidOperationException(errorMessage);
        }
    }

对于我的测试,我会模拟ParsePlayerProfileData和MatchPlayerInDatabase,但是为了测试.Games属性的设置,我需要向Player添加一个IPlayer接口。 然后我将ParsePlayerProfileData和MatchPlayerInDatabase返回接口。

DTOProfile.IPlayer player = this._playerDataParser.ParsePlayerProfileData(playerRecord);

DTOProfile.IPlayer playerInDatabase = this._playerManager.MatchPlayerInDatabase(player.Id);

然后在我的测试中,我有: -

var mockUpdatedPlayer = new Mock<IPlayer>();
var mockDatabasePlayer = new Mock<IPlayer>();
_playerDataParser.Setup(p => p.ParsePlayerProfileData(It.IsAny<DataRow>())).Returns(mockUpdatedPlayer.Object);
_playerManager.Setup(m => m.MatchPlayerInDatabase(It.IsAny<int>())).Returns(mockDatabasePlayer.Object);

UpdatePlayerProfileStatistics(myRow, true);

mockedPlayer.VerifySet(x => x.Games = It.IsAny<int>(), Times.Never());

这一切都有效。

我的问题是,如果我有所有DTO的接口,并且让我的所有方法都返回这些接口(而不是具体类型),那么向前推进? 我认为这将允许我执行这些'VerifySet'和'VerifyGet'。

或者,有没有更好的方法来测试它?

2 个答案:

答案 0 :(得分:2)

你不想模仿DTO,正确构造的DTO是你测试的结果,因此也是你想要反对的东西。

该方法应该返回有问题的DTO实例,然后您可以对其进行验证。没有必要的接口。

如果你模仿交互的每个方面,你所测试的只是模拟框架的工作原理。

我的直觉是你的方法在做太多了......重构的建议:

public DTOProfile.Player UpdatePlayerProfileStatistics(DataRow playerRecord, bool useUpdatedGamesPlayed = true)
{
    DTOProfile.Player player = this._playerDataParser.ParsePlayerProfileData(playerRecord);

    DTOProfile.Player playerInDatabase = this._playerManager.MatchPlayerInDatabase(player.Id);

    string errorMessage = String.Empty;
    if (useUpdatedGamesPlayed)
    {
        // do nothing. player.Games = player.Games; nothing changes
    }
    else
    {
        // we want to keep the Games played from the value of the player in the database.
        player.Games = playerInDatabase.Games;
    }

    return player;
}

public void PersistPlayer(DTOProfile.Player player) 
{
    if (!this.repository.UpdatePlayerProfile(player, out errorMessage))
    {
        throw new InvalidOperationException(errorMessage);
    }
}

然后您可以测试两种方法:

  • 给定DataRow,玩家对象会正确更新。
  • 给定Player实例,如果持久化失败,则会抛出异常,在各种情况下生成正确的错误消息等。

答案 1 :(得分:1)

我认为使用仅用于测试的代码修改类是一种设计气味。 它会使您的DTO混乱并导致代码重复。此外,所有接口 将基本上重复在DTO中找到的所有属性,并添加/删除/修改属性 你现在需要改变两个地方而不是一个。

您可以在不模仿DTO的情况下重写单元测试:

var mockUpdatedPlayer = new DTOProfile.Player();
var mockDatabasePlayer = new DTOProfile.Player();
_playerDataParser.Setup(p => p.ParsePlayerProfileData(It.IsAny<DataRow>())).Returns(mockUpdatedPlayer);
_playerManager.Setup(m => m.MatchPlayerInDatabase(It.IsAny<int>())).Returns(mockDatabasePlayer);
UpdatePlayerProfileStatistics(myRow, true);
Assert.AreNotEqual(mockUpdatedPlayer.Games, mockDatabasePlayer.Games);

或者,如果没有空的玩家构造函数,或者构造对于测试来说太重了, 您可以将DTO属性标记为虚拟 - Moq可以模拟虚拟属性。

支持我观点的其他几个链接:

http://rrees.me/2009/01/31/programming-to-interfaces-anti-pattern/

http://marekdec.wordpress.com/2011/12/06/explicit-interface-per-class-antipattern/

对于完全相反的观点:

Are single implementer interfaces for unit testing an antipattern?