Skip to content

Latest commit

 

History

History
executable file
·
206 lines (131 loc) · 12.1 KB

Mockito16-26.md

File metadata and controls

executable file
·
206 lines (131 loc) · 12.1 KB
  1. 真实的局部mocks (1.8版本之后)

在内部通过邮件进行了无数争辩和讨论后,最终 Mockito 决定支持部分测试,早前我们不支持是因为我们认为部分测试会让代码变得糟糕。然而,我们发现了部分测试真正合理的用法。详情点这

在 Mockito 1.8 之前,spy() 方法并不会产生真正的部分测试,而这无疑会让一些开发者困惑。更详细的内容可以看:这里Java 文档

    //you can create partial mock with spy() method:
    List list = spy(new LinkedList());

    //you can enable partial mock capabilities selectively on mocks:
    Foo mock = mock(Foo.class);
    //Be sure the real implementation is 'safe'.
    //If real implementation throws exceptions or depends on specific state of the object then you're in trouble.
    when(mock.someMethod()).thenCallRealMethod();

一如既往,你会去读部分测试的警告部分:面向对象编程通过将抽象的复杂度拆分为一个个独立,精确的 SRPy 对象中,降低了抽象处理的复杂度。那部分测试是怎么遵循这个规范的呢?事实上部分测试并没有遵循这个规范……部分测试通常意味着抽象的复杂度被移动到同一个对象的不同方法中,在大多数情况下,这不会是你想要的应用架构方式。

然而,在一些罕见的情况下部分测试才会是易用的:处理不能轻易修改的代码(第三方接口,临时重构的遗留代码等等)。然而,为了新的,测试驱动和架构优秀的代码,我是不会使用部分测试的。

  1. 重置mocks对象 (1.8版本之后)

聪明的 Mockito 使用者很少会用到这个特性,因为他们知道这是出现糟糕测试单元的信号。通常情况下你不会需要重设你的测试单元,只需要为每一个测试方法重新创建一个测试单元就可以了。

如果你真的想通过 reset() 方法满足某些需求的话,请考虑实现简单,小而且专注于测试方法而不是冗长,精确的测试。首先可能出现的代码异味就是测试方法中间那的 reset() 方法。这可能意味着你已经过度测试了。请遵循测试方法的呢喃:请让我们小,而且专注于单一的行为上。在 Mockito 邮件列表中就有好几个讨论是和这个有关的。

添加 reset() 方法的唯一原因就是让它能与容器注入的测试单元协作。详情看 issue 55FAQ

别自己给自己找麻烦,reset() 方法在测试方法的中间确实是代码异味。

   List mock = mock(List.class);
   when(mock.size()).thenReturn(10);
   mock.add(1);

   reset(mock);
   //at this point the mock forgot any interactions & stubbing
  1. 故障排查与验证框架的使用 (1.8版本之后)

首先,如果出现了任何问题,我建议你先看 Mockito FAQ

任何你提的问题都会被提交到 Mockito 的邮件列表中。

然后你应该知道 Mockito 会验证你是否始终以正确的方式使用它,对此有疑惑的话不妨看看 validateMockitoUsage() 的文档说明。

19.行为驱动开发的别名 (1.8版本之后)

行为驱动开发实现测试单元的模式将 //given //when //then comments 视作测试方法的基础,这也是我们实现单元测试时被建议做的!

你可以在这开始学习有关 BDD 的知识

问题是当信息没有很好地与 //given //when //then comments 交互时,扮演规范角色的测试桩 API 就会出现问题。这是因为测试桩属于给定测试单元的组件,而且不是任何测试的组件。因此 BDDMockito 类介绍了一个别名,使你的测试桩方法调用 BDDMockito.given(Object) 方法。现在它可以很好地和给定的 BDD 模式的测试单元组件进行交互。

 import static org.mockito.BDDMockito.*;

 Seller seller = mock(Seller.class);
 Shop shop = new Shop(seller);

 public void shouldBuyBread() throws Exception {
   //given
   given(seller.askForBread()).willReturn(new Bread());

   //when
   Goods goods = shop.buyBread();

   //then
   assertThat(goods, containBread());
 }
  1. 序列化mock对象

模拟对象可以被序列化。有了这个特性你就可以在依赖被序列化的情况下使用模拟对象了。

警告:这个特性很少在单元测试中被使用。

To create serializable mock use MockSettings.serializable():

这个特性通过 BDD 拥有不可考外部依赖的特性的具体用例实现,来自外部依赖的 Web 环境和对象会被序列化,然后在不同层之间被传递。

   List serializableMock = mock(List.class, withSettings().serializable());

The mock can be serialized assuming all the normal serialization requirements are met by the class.

模拟对象能被序列化假设所有普通的序列化要求都被类满足了。

让一个真实的侦查对象可序列化需要多一些努力,因为 spy(...) 方法没有接收 MockSettings 的重载版本。不过不用担心,你几乎不可能用到这。

 List<Object> list = new ArrayList<Object>();
 List<Object> spy = mock(ArrayList.class, withSettings()
                 .spiedInstance(list)
                 .defaultAnswer(CALLS_REAL_METHODS)
                 .serializable());
  1. 新的注解 : @Captor,@Spy,@ InjectMocks (1.8.3版本之后)

V1.8.3 带来的新注解在某些场景下可能会很实用

@Captor 简化 ArgumentCaptor 的创建 - 当需要捕获的参数是一个令人讨厌的通用类,而且你想避免编译时警告。

@Spy - 你可以用它代替 spy(Object) 方法

@InjectMocks - 自动将模拟对象或侦查域注入到被测试对象中。需要注意的是 @InjectMocks 也能与 @Spy 一起使用,这就意味着 Mockito 会注入模拟对象到测试的部分测试中。它的复杂度也是你应该使用部分测试原因。

所有新的注解仅仅在 MockitoAnnotations.initMocks(Object) 方法中被处理,就像你在 built-in runner 中使用的 @Mock 注解:MockitoJUnitRunner 或 规范: MockitoRule.

  1. 验证超时 (1.8.5版本之后)

允许带有暂停的验证。这使得一个验证去等待一段特定的时间,以获得想要的交互而不是如果还没有发生事件就带来的立即失败。在并发条件下的测试这会很有用。

感觉起来这个特性应该很少被使用 - 指出更好的测试多线程系统的方法。

还没有实现去和 InOrder 验证协作。

例子:

   //passes when someMethod() is called within given time span
   verify(mock, timeout(100)).someMethod();
   //above is an alias to:
   verify(mock, timeout(100).times(1)).someMethod();

   //passes when someMethod() is called *exactly* 2 times within given time span
   verify(mock, timeout(100).times(2)).someMethod();

   //passes when someMethod() is called *at least* 2 times within given time span
   verify(mock, timeout(100).atLeast(2)).someMethod();

   //verifies someMethod() within given time span using given verification mode
   //useful only if you have your own custom verification modes.
   verify(mock, new Timeout(100, yourOwnVerificationMode)).someMethod();
  1. 自动初始化被@Spies, @InjectMocks注解的字段以及构造函数注入 (1.9.0版本之后)

Mockito 现在会通过注入构造方法、setter 或域注入尽可能初始化带有 @Spy 和 @InjectMocks 注解的域或方法。

为了利用这一点特性,你需要使用 MockitoAnnotations.initMocks(Object), MockitoJUnitRunnerMockitoRule

为了 InjectMocks 请在 Java 文档中了解更多可用的技巧和注入的规范

 //instead:
 @Spy BeerDrinker drinker = new BeerDrinker();
 //you can write:
 @Spy BeerDrinker drinker;

 //same applies to @InjectMocks annotation:
 @InjectMocks LocalPub;
  1. 单行测试桩 (1.9.0版本之后)

Mockito 现在允许你在使用测试桩时创建模拟对象。基本上,它允许在一行代码中创建一个测试桩,这对保持代码的整洁很有用。举例来说,有些乏味的测试桩会被创建,并在测试初始化域时被打入,例如:

 public class CarTest {
   Car boringStubbedCar = when(mock(Car.class).shiftGear()).thenThrow(EngineNotStarted.class).getMock();

   @Test public void should... {}
  1. 验证被忽略的测试桩 (1.9.0版本之后)

Mockito 现在允许为了验证无视测试桩。在与 verifyNoMoreInteractions() 方法或验证 inOrder() 方法耦合时,有些时候会很有用。帮助避免繁琐的打入测试桩调用验证 - 显然我们不会对验证测试桩感兴趣。

警告,ignoreStubs() 可能会导致 verifyNoMoreInteractions(ignoreStubs(...)) 的过度使用。谨记在心,Mockito 没有推荐用 verifyNoMoreInteractions() 方法连续地施用于每一个测试中,原因在 Java 文档中有。

一些例子:

 verify(mock).foo();
 verify(mockTwo).bar();

 //ignores all stubbed methods:
 verifyNoMoreInvocations(ignoreStubs(mock, mockTwo));

 //creates InOrder that will ignore stubbed
 InOrder inOrder = inOrder(ignoreStubs(mock, mockTwo));
 inOrder.verify(mock).foo();
 inOrder.verify(mockTwo).bar();
 inOrder.verifyNoMoreInteractions();

更好的例子和更多的细节都可以在 Java 文档的 ignoreStubs(Object...) 部分看到。

  1. mock详情 (1.9.5版本之后)

为了区别一个对象是模拟对象还是侦查对象:

     Mockito.mockingDetails(someObject).isMock();
     Mockito.mockingDetails(someObject).isSpy();

MockingDetails.isMock()MockingDetails.isSpy() 方法都会返回一个布尔值。因为一个侦查对象只是模拟对象的一种变种,所以 isMock() 方法在对象是侦查对象是会返回 true。在之后的 Mockito 版本中 MockingDetails 会变得更健壮,并提供其他与模拟对象相关的有用信息,例如:调用,测试桩信息,等等……