Unit testing 确保方法A在方法B之后调用的单元测试

Unit testing 确保方法A在方法B之后调用的单元测试,unit-testing,Unit Testing,我有一个与某个对象交互的代码,然后应该对它调用finish()方法 void completeTransaction(PaymentTransaction transaction) { recordTransaction(transaction.getId()); transaction.finish(); } PaymentTransaction是一些第三方类,finish()之后的行为是未定义的-它可能会引发异常或只是无声地失败 我需要编写一个单元测试,该测试通过了,

我有一个与某个对象交互的代码,然后应该对它调用finish()方法

void completeTransaction(PaymentTransaction transaction) {  
    recordTransaction(transaction.getId());

    transaction.finish();
}
PaymentTransaction是一些第三方类,
finish()
之后的行为是未定义的-它可能会引发异常或只是无声地失败

我需要编写一个单元测试,该测试通过了,并且只有通过了:

  • recordTransaction(transaction.getId())
    调用
  • transaction.finish()
    调用
  • transaction.finish()
    recordTransaction(transaction.getId())之后调用
  • 满足上述条件的测试应禁止此类代码:

    void completeTransaction(PaymentTransaction transaction) {
       transaction.finish();
       recordTransaction(transaction.getId()); //oops
    }
    
    第一个条件的测试用例:

    void testCompleteTransaction_TransactionRecorded() {
        completeTransaction(transactionMock);
        // assert that recordTransaction(transaction.getId()) 
        // called with correct argument
    
    completeTransaction(付款交易) } 关于第二个问题:

    void testCompleteTransaction_TransactionCompleted() {
        completeTransaction(transactionMock);
        // assert that transaction.finish() called
    }
    

    我想知道如何通过测试用例强制执行第三个条件。

    您的测试用例毫无意义:

    方法在代码中的调用顺序如下: 在单元测试中,您不仅应该调用一些方法,还应该测试正确的结果

    但如果你想找点乐子:

    public testNonsenseTest() {
         int i  = 0;
         PaymentTransaction transaction = new PaymentTransaction();
         int transactionId = transaction.getId());
         recordTransaction(transactionId);
         i++;
         assertEquals(1, i);
         transaction.finish();
         i++;
         assertEquals(2, i);
    
    }
    

    您可以传入一个伪
    PaymentTransaction
    ,该事务覆盖
    finish()
    getId()
    ,这样
    finish()
    在调用
    getId()
    时,如果没有设置某个内部标志,就会引发异常

    public class FakePaymentTransaction {
        private bool _getIdWasCalled = false;
    
        public override void finish () {
            if (!_getIdWasCalled) {
                throw new Exception ("getId wasn't called first!");
            }
        }
    
        public override /* your return type */ getId() {
            _getIdWasCalled = true;
            // Some other logic to return your specified return type
        }
    }
    

    现在,当您将其传递到SUT时,您将看到调用的顺序是否正确。

    您需要的是一个可以验证调用顺序是否符合预期的模拟。你可以按照James D'Angelo的回答中的建议,针对具体的案例展开你自己的研究,或者你可以创建一个更通用的类似的研究

    或者您可以使用一个好的模拟框架提供的工具


    例如,Mockito有一个验证器,可以验证来自单个或多个模拟的模拟方法的调用顺序。

    涉及哪些测试框架。这是什么语言?您可以显示1和2的现有测试吗?您缺少一些解释:只要RecordTransaction不创建线程,当您从调用返回时,它就完成了。是否要在recordTransaction()中等待另一个线程?@Damien_The_unsiver:这段代码是用某种虚构的语言编写的,用于将问题与特定语言的详细信息分开。@AlexWien:否,所有这些都是关于以正确的顺序调用方法。我编辑了我的问题,以强调重点并使其更加清晰。我认为它们在白盒单元测试方面是有意义的:我定义了代码预期要做的事情。这里的结果是记录事务并确保它已完成。至少它是有用的,您可以在启动测试用例时调试到其中。您无法测试transactionId是否与另一个transactionId不同。对不起,我忘了在测试用例中进行实际调用。这就是你的观点?不知道你的代码是很难的。如果一个交易不成功会发生什么?它会抛出一个例外吗?如果是,那么类似于我的测试是有意义的,它表明,至少可以在没有异常的情况下调用methdos。recordTranaction()的返回值是多少?它是布尔值还是空值。如果是布尔值:您可以执行assertTrue(recordTransaction(TrasActionId));非常感谢。我想没有比这更轻量级的解决方案了。我不同意。这是一个相当轻量级的解决方案。您可以控制SUT的输入,以便准确定义内部发生的事情。没有什么“神奇”的方法可以做到这一点。我同意。因为OP中没有语言,我觉得我不能给出更具体的语言/框架的答案。@JamesD'Angelo:你的答案很好,我投了赞成票。我只是觉得一些框架提供的帮助可能也很有用。谢谢大家的支持。我不是故意让人觉得我是在自卫。只希望能提供更多信息,以便我们能给出更好的答案。=)