【问题标题】:When do I use an interface on a DTO model?何时在 DTO 模型上使用接口?
【发布时间】:2023-04-05 15:29:01
【问题描述】:

我正在使用 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”。

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

【问题讨论】:

    标签: .net unit-testing moq


    【解决方案1】:

    您不想模拟 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实例,如果持久化失败会抛出异常,在各种情况下都会产生正确的错误信息等等。

    【讨论】:

      【解决方案2】:

      我认为用仅用于测试的代码修改你的类是一种设计味道。 它会使您的 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?

      【讨论】:

      • 是的,设计气味正是我所指的。我喜欢拥有VerifySet 和VerifyGet 方法的想法....但是为了使用它们而不得不转换我的代码库的大块真的不喜欢我。我喜欢虚拟属性的想法。我会尝试一下,看看是否能减轻我的一些担忧。
      猜你喜欢
      • 2015-11-02
      • 2021-09-28
      • 2018-10-15
      • 1970-01-01
      • 2016-10-05
      • 1970-01-01
      • 1970-01-01
      • 2017-05-09
      • 1970-01-01
      相关资源
      最近更新 更多