Тестирование кода, который выполняется в цикле - PullRequest
10 голосов
/ 23 июня 2011

Я думал, что попробую кодовое ката, которое имитирует банкомат в командной строке.Я решил управлять дизайном, используя TDD.Я столкнулся с интересным сценарием, и мне любопытно, что другие сделали в этом сценарии.

Если вы посмотрите (внизу) на мой класс AtmMachine, вы заметите, что янамеренно выходить из цикла while, чтобы время моих тестов не истекло.Это похоже на запах кода для меня, и я хотел бы знать, если другие делают такие вещи.

Мои нынешние чувства разделились между:

  1. "Я делаю этонеправильно "при попытке выполнить модульное тестирование текущего выполнения
  2. Невозможность написать для него модульный тест подразумевает, что" while "является неправильной конструкцией

Вот мои тестына данный момент для банкомата:

[TestClass]
public class when_atm_starts
{
    private static readonly string WELCOME_MSG = "Welcome to Al Banco de Ruiz!";

    private AtmMachine _atm;
    private Mock<IAtmInput> _inputMock;
    private Mock<IAtmOutput> _outputMock;
    private Mock<ILogger> _loggerMock;
    private Mock<ICommandFactory> _cmdFactoryMock;


    [TestInitialize]
    public void BeforeEachTest()
    {
        _inputMock = new Mock<IAtmInput>();
        _outputMock = new Mock<IAtmOutput>();
        _loggerMock = new Mock<ILogger>();
        _cmdFactoryMock = new Mock<ICommandFactory>();

        _atm = new AtmMachine(_inputMock.Object, _outputMock.Object, _loggerMock.Object, _cmdFactoryMock.Object);
    }


    [TestMethod]
    public void no_one_should_be_logged_in()
    {
        this.SetupForCancelledUser();

        _atm.Start();

        Assert.IsNull(_atm.CurrentUser);
    }

    [TestMethod]
    public void should_print_welcome_to_output()
    {
        this.SetupForCancelledUser();

        _atm.Start();

        _outputMock.Verify(o => o.Write(WELCOME_MSG));
    }

    [TestMethod]
    public void should_execute_login_command()
    {
        Mock<ILoginCommand> loginCmdMock = new Mock<ILoginCommand>();

        _cmdFactoryMock.Setup(cf => cf.GetLoginCommand(_inputMock.Object, _outputMock.Object))
            .Returns(loginCmdMock.Object);

        loginCmdMock.Setup(lc => lc.LogonUser())
            .Returns(AtmUser.CancelledUser);

        _atm.Start();

        loginCmdMock.Verify(lc => lc.LogonUser());
    }


    private void SetupForCancelledUser()
    {
        Mock<ILoginCommand> loginCmdMock = new Mock<ILoginCommand>();

        _cmdFactoryMock.Setup(cf => cf.GetLoginCommand(_inputMock.Object, _outputMock.Object))
            .Returns(loginCmdMock.Object);

        loginCmdMock.Setup(lc => lc.LogonUser())
            .Returns(AtmUser.CancelledUser);
    }
}

А вот соответствующий класс AtmMachine.

public class AtmMachine
{
    public static readonly string WELCOME_MSG = "Welcome to Al Banco de Ruiz!";


    private bool _shouldContinue;
    private ILogger _log;
    private ICommandFactory _cmdFactory;
    private IAtmInput _input;
    private IAtmOutput _output;


    public object CurrentUser { get; set; }


    public AtmMachine(
        IAtmInput input,
        IAtmOutput output,
        ILogger logger,
        ICommandFactory cmdFactory)
    {
        this._input = input;
        this._output = output;
        this._log = logger;
        this._cmdFactory = cmdFactory;
    }


    public void Start()
    {
        _shouldContinue = true;

        while (_shouldContinue)
        {
            _output.Clear();
            _output.Write(WELCOME_MSG);
            AtmUser user = this.GetNextUser();

            if (user == AtmUser.CancelledUser) { _shouldContinue = false; }

            _shouldContinue = false;
        }
    }


    private AtmUser GetNextUser()
    {
        ILoginCommand loginCmd = _cmdFactory.GetLoginCommand(_input, _output);
        return loginCmd.LogonUser();
    }
}

Ответы [ 2 ]

11 голосов
/ 24 июня 2011

Вы правы в отношении проверки цикла таким образом.У меня нет особого контекста в отношении функциональности, которую вы пытаетесь протестировать, кроме «никто не вошел», поэтому я воспользуюсь некоторыми предложениями.

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

// In your AtmMachine
public void Start()
{
    _shouldContinue = true;

    while (stillRunning())
    {
        // Do some ATM type stuff
    }
}

protected virtual bool stillRunning() {
    return _shouldContinue;
}

Внутри теста вы можете создать специальный класс теста, который переопределяет stillRunning().

// Inside your test
[TestMethod]
public void no_one_should_be_logged_in()
{
    _atm = new AtmThatImmediatelyShutsDown();

    this.SetupForCancelledUser();

    _atm.Start();

    Assert.IsNull(_atm.CurrentUser);
}

class AtmThatImmediatelyShutsDown : AtmMachine {
    protected override bool stillRunning() {
        return false;
    }
}

Другой вариант - ввести условие как класс / интерфейс, который можно смоделировать.Выбор за вами.

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

// In your AtmMachine
public void Start()
{
    _shouldContinue = true;

    while (stillRunning())
    {
        processCommands();
    }
}

public void processCommands() {
    ...
}

Теперь вы можете напрямую вызвать метод processCommands() и пропустить все циклы.

Надеюсь, это поможет!

Брэндон

2 голосов
/ 24 июня 2011

Похоже на место, где можно применить SingleResponsibilityPrinciple.

  1. Демон или служба, которая ожидает / зацикливается на триггере
  2. SomeClass, который обслуживает AtmUser / обрабатывает транзакцию.

Если вы разделите эти несвязанные обязанности на две роли, вам будет намного легче протестировать их обе.

public void Start()
    {
        while (_shouldContinue)
        {
            _output.Clear();
            _output.Write(WELCOME_MSG);
            if (HasUserLoggedIn)
               SomeOtherType.ProcessTransaction();
        }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...