Я пытаюсь создать модульные тесты для частного метода - PullRequest
0 голосов
/ 29 апреля 2018

Я пытаюсь создать модульные тесты для метода 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 голосов
/ 02 мая 2018

Как уже упоминалось другими, не должно иметь значения, что делает приватный метод с точки зрения модульного теста. Все, что вас волнует, это то, что, если вы будете толкать или подталкивать объект правильным образом, он окажется в нужном состоянии.

Вот как вы можете добиться этого, используя интерфейсы и Moq.

Во-первых, извлеките интерфейс, представляющий свойства и методы, необходимые для выполнения действия. Я абстрагировал ваш Console.WriteLine, потому что он значительно облегчает тестирование (и даже открывает другие возможности для использования этого кода в не консольном приложении). Нам на самом деле не нужны «кости» как таковые. На самом деле нам нужен объект, который мы можем попросить 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());
    }

Теперь вы, вероятно, захотите немного больше подумать о дизайне и о том, действительно ли плитка несет ответственность за обновление этих свойств, или же она должна вызывать что-то для объекта тюрьмы, который можно отдельно протестировать, но это показывает, как вы можете использовать макеты для абстрагирования вызовов случайных и т. д. от кода, чтобы сделать его тестируемым.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...