我正在使用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'。
或者,有没有更好的方法来测试它?
答案 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?