在单元测试中计算方法调用次数

89

什么是在单元测试中计算方法调用次数的最佳方法?是否有任何测试框架可以实现这一点?


你能详细说明一下你想做什么吗?你是想测量代码覆盖率吗?还是性能?或者是效率? - Mark Robinson
不是很想。只是想检查当我调用testXXX()测试方法时,类中的foo()方法至少被调用了N次。 - user855
这不可能吗?我知道Mocking框架允许我对Mock对象的调用次数进行断言。难道在真实对象上做不到吗? - user855
7个回答

108

听起来你可能想使用模拟框架通常提供的.expects(1)类型方法。

使用Mockito,如果你正在测试一个List并且想要验证clear被调用了3次,add至少被调用了一次且具有这些参数,则可以按照以下方式进行:

List mock = mock(List.class);        
someCodeThatInteractsWithMock();                 

verify(mock, times(3)).clear();
verify(mock, atLeastOnce()).add(anyObject());      

来源 - Mockito vs EasyMock


58

在 Mockito 中,您可以像这样实现:

YourService serviceMock = Mockito.mock(YourService.class);

// code using YourService

// details of all invocations including methods and arguments
Collection<Invocation> invocations = Mockito.mockingDetails(serviceMock).getInvocations();
// just a number of calls of any mock's methods
int numberOfCalls = invocations.size();

我发现这是使用中最简单和最有效的方法,前提是您可以访问模拟对象。 - iZian
谢谢Jakub,这也很好,因为如果您检查对象,您可以看到调用了哪些函数以及它们的作用。 - Stoffe
4
可以获取所有调用。应该有一种方法可以获取单个方法的调用,而无需流式处理和筛选集合,并使用 Reflection 获取要进行“等于”比较的 Method 对象。参考 Sinon 的 JS API:stub.callCount - oligofren

18

假设有一个名为“RoleRepository”的类,其拥有一个单独的方法“getRole(String user)”,该方法将返回一个角色。

假设您已经将此对象声明为Mock或Spy,并且想要检查方法getRole(String)是否被调用了一次。

您可以这样做:Mockito.verify(roleRepository, Mockito.times(1)).getRole(Mockito.anyString());

import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;

@RunWith(MockitoJUnitRunner.class)
public class RoleRepositoryTest {

    @Spy
    private RoleRepository roleRepository = new RoleRepository();

    @Test
    public void test() {
        roleRepository.getRole("toto");
        Mockito.verify(roleRepository, Mockito.times(1)).getRole(Mockito.anyString());
    }

    public static class RoleRepository {

        public String getRole(String user) {
            return "MyRole";
        }
    }
}

9
使用Mockito中的Answer接口,您可以计算方法调用的数量。
ConnectionPool mockedConnectionPool = mock(ConnectionPool.class);

    final int[] counter = new int[1];

    when(mockedConnectionPool.getConnection()).then(new Answer<Connection>() {

        @Override
        public Connection answer(InvocationOnMock invocation) throws Throwable {
            counter[0]++;
            return conn;
        }

    }); 
    // some your code

    assertTrue(counter[0] == 1);

5

根据您想要计数的方法,您可以创建一个测试配置,其中包含与您的类/包/方法匹配的@Before建议:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

@Aspect
public class MethodCounterAspect {

    private int counter = 0 // or inject the Counter object into this aspect

    @Pointcut( "execution( * com.sample.your.package.*.*(..) )" )
    public void methodsToCount() {}

    @Before("methodsToCount()")
    public void execute() throws Throwable {
        counter++; // or update the counter injected into this aspect..
    }

    // get the counter
}

如果您觉得更容易使用,可以使用原始的AspectJ或通过以上XML配置使用Spring AOP。

如果需要,您可以创建不同的切入点/方面。


如果你只想查看调用次数,这是实现的方法。如果你想验证调用次数,模拟框架通常会使用断言来包装这个模式。请参见我的答案以获取模拟版本。 - case nelson
问题是“在单元测试中计算方法调用的最佳方法是什么”。上述内容将使您能够在单元/集成测试中计算方法调用,而不会污染测试本身。 - tolitius
我同意,这就是为什么我投了你的赞。但在评论中,作者询问了关于实际对象和模拟对象调用计数断言的问题。如果他想要真正测试调用次数,那么他应该使用一个模拟库,因为这就是它们的设计目的。 - case nelson

3

Mockito.spy() 的链接似乎已经失效。 - Samarth S

1

你有几个选项:

1)添加一些特殊代码来计算函数中的调用次数。这样做可以实现,但并不是一个好的解决方案。

2)在运行单元测试后,检查代码覆盖率。大多数覆盖工具会计算调用次数,但它们真正设计的是后处理。

3)使用分析器。分析器将让您计算函数被调用的次数。这是一个非常手动的过程,因此并不是为单元测试而设计的。

更好的解决方案是检查输出是否符合预期,而不是检查其内部工作方式。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接