循环执行的测试代码

| 我以为我会尝试在命令行上模拟ATM机的代码kata。我决定使用TDD驱动设计。我遇到了一个有趣的场景,并对其他人在此场景中所做的事情很好奇。 如果您查看(在底部向下)我的AtmMachine类,您会注意到我故意退出了while循环,这样我的测试就不会超时。这对我来说就像是代码的气味,我想知道其他人是否这样做。 我目前的感受在以下方面分裂: 尝试对正在进行的执行进行单元测试,从而“我做错了” 无法为其编写单元测试意味着“ while”是错误的构造 到目前为止,这是我对atm机器的单元测试:
[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();
    }
}
    
已邀请:
        您以这种方式测试循环是正确的。除了“没有人登录”以外,关于您尝试测试驱动器的功能,我没有太多的背景信息,因此我会为我的建议取一些自由。 首先,对于循环测试,您可以根据自己的喜好采用几种方法。第一种方法是将循环条件提取到可以在用于测试的子类中重写的方法中。
// In your AtmMachine
public void Start()
{
    _shouldContinue = true;

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

protected virtual bool stillRunning() {
    return _shouldContinue;
}
在测试内部,您可以创建一个覆盖ѭ3的特殊测试类。
// 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()
方法并跳过所有循环。 希望有帮助! 布兰登     
        似乎是一个应用SingleResponsibilityPrinciple的地方。 等待/循环等待触发器的守护程序或服务 服务AtmUser的SomeClass /处理事务。 如果将这些不相关的职责划分为两个角色,则测试这两个角色将变得更加容易。
public void Start()
    {
        while (_shouldContinue)
        {
            _output.Clear();
            _output.Write(WELCOME_MSG);
            if (HasUserLoggedIn)
               SomeOtherType.ProcessTransaction();
        }
}
    

要回复问题请先登录注册