我试图为私有方法创建单元测试

时间:2018-04-29 17:11:52

标签: c# unit-testing

我正在尝试为GetOutOfJail方法创建单元测试,但我无法找到一种方法来获取它,因为它是私有的,除了测试之外没有必要公开。我无法在继承抽象类LandedOnTile时更改Tile方法的签名。

正如你可能已经解决的那样,这是一场垄断游戏,我试图制作一个迷你项目。

public abstract class Tile
{
    public abstract int Location { get;}
    public abstract void LandedOnTile(Player player);
}

public class JailTile : Tile
{
    public override int Location { get; }
    Random dice = new Random();

    public JailTile()
    {
        Location = 3;
    }

    public override void LandedOnTile(Player player)
    {
        if (player.inJail)
        {
            GetOutOfJail(player);
        }
        else
        {
            Console.WriteLine(player.name + " is just visiting jail");
        }
    }

    private void GetOutOfJail(Player player)
    {
        int roll = dice.Next(1, 4);
        int turnsInJail = player.timeInJail;

        if (turnsInJail == 3)
        {
            player.inJail = false;
            Console.WriteLine(player.name + " has spent 3 turns in jail and is now out");
            player.timeInJail = 0;
        }
        else if (turnsInJail < 3 && roll > 2)
        {
            player.inJail = false;
            Console.WriteLine(player.name + " has rolled a 3 and it out of jail");
            player.timeInJail = 0;
        }
        else
        {
            Console.WriteLine(player.name + " has rolled a lower than a 3 and is in jail for another turn");
            player.timeInJail++;
        }
    }
}

1 个答案:

答案 0 :(得分:0)

正如其他人所提到的,从单元测试的角度来看,私有方法的作用并不重要。你所关心的只是如果你以正确的方式戳戳或刺激物体,它就会以正确的状态结束。

以下是使用接口和Moq实现这一目标的方法。

首先,提取表示执行操作所需的属性和方法的接口。我已经抽象出你的Console.WriteLine,因为它使测试更容易(甚至打开了在非控制台应用程序中使用该代码的其他机会)。我们实际上并不需要一个&#34;骰子&#34;本身。我们实际需要的是一个对象,我们可以向Roll()询问并得到一个int。玩家可能对它们有自己的业务规则,因此提取到IPlayer接口允许我对JailTile的测试忽略这些事情。

public interface ILogger
{
    void LogMessage(string message);
}

public interface IDice
{
    int Roll();
}

public interface IPlayer
{
    string Name
    {
        get;
    }

    bool InJail
    {
        get;
        set;
    }

    int TimeInJail
    {
        get;
        set;
    }
}

其次,这是Dice和ConsoleLogger的具体实现。您可以在生产代码中传递这些内容,而不是我在测试用例中使用的模拟

public class ConsoleLogger : ILogger
{
    public void LogMessage(string message)
    {
        Console.WriteLine(message);
    }
}

public class Dice : IDice
{
    private readonly Random random = new Random();
    public int Roll()
    {
        return this.random.Next(1, 6);
    }
}

第三,这里是你的Tile和JailTile类略微修改以使用构造函数注入

public abstract class Tile
{
    protected readonly IDice Dice;
    protected readonly ILogger Logger;
    protected Tile(ILogger logger, IDice dice)
    {
        this.Logger = logger;
        this.Dice = dice;
    }

    public abstract int Location
    {
        get;
    }

    public abstract void LandedOnTile(IPlayer player);
}

public class JailTile : Tile
{
    public JailTile(ILogger logger, IDice dice): base (logger, dice)
    {
    }

    public override int Location => 3;
    public override void LandedOnTile(IPlayer player)
    {
        if (player.InJail)
        {
            this.GetOutOfJail(player);
        }
        else
        {
            this.Logger.LogMessage($"{player.Name} is just visiting jail");
        }
    }

    private void GetOutOfJail(IPlayer player)
    {
        int roll = this.Dice.Roll();
        int turnsInJail = player.TimeInJail;
        if (turnsInJail == 3)
        {
            player.InJail = false;
            this.Logger.LogMessage($"{player.Name} has spent 3 turns in jail and is now out");
            player.TimeInJail = 0;
        }
        else if (turnsInJail < 3 && roll > 2)
        {
            player.InJail = false;
            this.Logger.LogMessage($"{player.Name} has rolled a 3 and it out of jail");
            player.TimeInJail = 0;
        }
        else
        {
            this.Logger.LogMessage($"{player.Name} has rolled a lower than a 3 and is in jail for another turn");
            player.TimeInJail++;
        }
    }
}

最后,这是一个测试用例,用于证明您的jailTile.LandedOnTile()方法会对Player进行正确的更改,并在给定一组先决条件的情况下将正确的消息记录到控制台

    [Test]
    public void ShouldReleaseAfterThreeTurns()
    {
        // Arrange
        Mock<ILogger> loggerMock = new Mock<ILogger>();
        Mock<IDice> diceMock = new Mock<IDice>();
        diceMock.Setup(s => s.Roll()).Returns(2);
        Mock<IPlayer> playerMock = new Mock<IPlayer>();
        playerMock.Setup(s => s.Name).Returns("Adam G");
        playerMock.Setup(s => s.InJail).Returns(true);
        playerMock.Setup(s => s.TimeInJail).Returns(3);
        // Act
        JailTile jailTile = new JailTile(loggerMock.Object, diceMock.Object);
        jailTile.LandedOnTile(playerMock.Object);
        // Assert
        playerMock.VerifySet(v => v.InJail = false, Times.Once());
        playerMock.VerifySet(v => v.TimeInJail = 0, Times.Once());
        loggerMock.Verify(v => v.LogMessage("Adam G has spent 3 turns in jail and is now out"), Times.Once());
    }

现在你可能想要更多地考虑设计,以及更新这些属性是否真的是瓦片的责任,或者是否应该在可以单独测试的jail对象上调用某些东西,但这显示了如何使用模拟从代码中抽象出随机等的调用以使其可测试。