Unit testing 测试在循环中执行的代码

Unit testing 测试在循环中执行的代码,unit-testing,tdd,Unit Testing,Tdd,我想我应该尝试在命令行上模拟ATM机的代码kata。我决定使用TDD驱动设计。我遇到了一个有趣的场景,我很好奇其他人在这个场景中做了什么 如果你看我的AtmMachine类(在底部),你会注意到我故意退出while循环,这样我的测试就不会超时。对我来说,这感觉像是一种代码味道,我想知道其他人是否会这样做 我目前的感觉分为两种: “我做错了”,试图对正在进行的执行进行单元测试 无法为它编写单元测试意味着“while”是错误的构造 以下是迄今为止我对atm机进行的单元测试: [TestClass]

我想我应该尝试在命令行上模拟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);
        }
    }
    

    您这样测试循环是正确的。除了“无人登录”之外,我对您尝试试驾的功能没有太多的背景知识,所以我将对我的建议采取一些自由的态度

    首先,对于循环测试,根据您的偏好,您可以采用几种方法进行测试。第一种方法是将循环条件提取到可在用于测试的子类中重写的方法中

    // In your AtmMachine
    public void Start()
    {
        _shouldContinue = true;
    
        while (stillRunning())
        {
            // Do some ATM type stuff
        }
    }
    
    protected virtual bool stillRunning() {
        return _shouldContinue;
    }
    
    在测试内部,您可以创建一个特殊的测试类,该类重写
    stillRunning()

    另一个选项是将条件作为可模拟的类/接口注入。选择权在你

    其次,为了简单起见,我将把该循环的核心提取到一个具有更高可见性的方法中,以允许测试循环中的代码

    // In your AtmMachine
    public void Start()
    {
        _shouldContinue = true;
    
        while (stillRunning())
        {
            processCommands();
        }
    }
    
    public void processCommands() {
        ...
    }
    
    现在,您可以直接调用
    processCommands()
    方法并跳过所有循环

    希望有帮助


    布兰登似乎是一个应用单一责任原则的地方

  • 等待/循环触发器的守护进程或服务
  • 服务于AtmUser/处理事务的某个类
  • 如果您将这些不相关的职责分成两个角色,那么测试这两个角色就容易多了

    public void Start()
        {
            while (_shouldContinue)
            {
                _output.Clear();
                _output.Write(WELCOME_MSG);
                if (HasUserLoggedIn)
                   SomeOtherType.ProcessTransaction();
            }
    }
    

    我已将你的评论标记为有用。在接下来的几天里,我将尝试你的建议(我喜欢)。如果一切顺利,我会回来把这个作为答案!是的,这就是我一直在寻找的答案。这很有趣,因为这个周末我在看书,他们说的基本上和你一样。“倾听您的测试”-如果很难测试,您需要询问原因。我也在同样的情况下运行,但可能没有意义使用公共“processCommands”方法,该方法不应该由单元测试以外的任何东西调用。这可能会造成类的误用,并使维护变得更加困难…它可能会。当我第一次开始练习TDD时,我和你一样担心。我仔细观察了一段时间,我的担心没有被意识到。我们还做的是将代码标记为受保护,并让您的测试从被测试的类继承。我个人不太喜欢这个解决方案。谢谢是的,我自己的经验告诉我,TDD最终会促使我在测试或重复数据消除迫使我之前,将守护进程从命令处理中分离出来,我担心Kent Beck可能会从监视器中探出头来,把我打得落花流水。@John-我会反驳说,编写下一个单元测试的困难/阻力在于您的代码驱使您进行一些更改。为了编写一个新的测试,有时需要进行(p)重构;这样下一个测试就容易了,这是一个很好的反驳。我投了赞成票;谢谢你的回复@孟买——事实上,我对他的反对意见投了赞成票。在我的屏幕上,它旁边有一个橙色的向上箭头。我不知道它算数,但没关系。顺便说一句,你的问题对我很有用。
    // In your AtmMachine
    public void Start()
    {
        _shouldContinue = true;
    
        while (stillRunning())
        {
            processCommands();
        }
    }
    
    public void processCommands() {
        ...
    }
    
    public void Start()
        {
            while (_shouldContinue)
            {
                _output.Clear();
                _output.Write(WELCOME_MSG);
                if (HasUserLoggedIn)
                   SomeOtherType.ProcessTransaction();
            }
    }