当前位置:   article > 正文

单元测试实践篇:Mock

answers.calls_real_methods

淘系的技术发展已经有相当一段历史了,在历史的长河中总能沉淀出很多复杂的巨型项目,包罗多个业务,而且往往服务依赖比较复杂;再加上一些特殊环境变量的设置,想要在本地运行、debug 自测这种大型应用的难度越来越高;尤其是对环境不太熟悉的新人而言成本会更高。

这类应用的单元测试不能像微服务化的应用一样,可以方便的将整个 service 在本地 Run Test,但是依靠于日常开发部署环境的远程 debug、日志、Arthas 等工具定位项目自测联调中的问题又会显得格外的笨重,问题修复几秒钟,发布一次 10min 会成为严重的效率瓶颈。

如何高效的自测代码逻辑,如何不启动整个服务就能验证我的目标方法呢?那就是我今天要介绍的三板斧 Mockito + PowerMock + AssertJ

上手


Mock 框架能帮助我们 mock 待测试的类中使用到的外部服务依赖,分布式缓存,DB查询等复杂逻辑,让我们轻松验证待测试类的目标方法的逻辑,当遇到外部依赖时可通过存根 mock 对应的返回结果,从而专注于验证本方法的逻辑正确性,而且跑单元测试不用把整个项目在本地跑起来,只会把当前测试所用到的类加载出来。换言之,Mock 能让代码对外部系统(或复杂依赖)隔离,不需要进行各种初始化操作。在假设外部依赖都能如预期返回的情况下验证自身逻辑的自洽性。

talk is cheap,show me your code.  开始盘它~

 配置 Maven 依赖

  1. <dependency>
  2. <groupId>junit</groupId>
  3. <artifactId>junit</artifactId>
  4. <version>4.11</version>
  5. <scope>test</scope>
  6. </dependency>
  7. <dependency>
  8. <groupId>org.mockito</groupId>
  9. <artifactId>mockito-core</artifactId>
  10. <version>3.5.2</version>
  11. <scope>test</scope>
  12. </dependency>
  13. <dependency>
  14. <groupId>org.powermock</groupId>
  15. <artifactId>powermock-module-junit4</artifactId>
  16. <version>2.0.5</version>
  17. <scope>test</scope>
  18. </dependency>
  19. <dependency>
  20. <groupId>org.powermock</groupId>
  21. <artifactId>powermock-api-mockito2</artifactId>
  22. <version>2.0.5</version>
  23. <scope>test</scope>
  24. </dependency>
  25. <dependency>
  26. <groupId>org.assertj</groupId>
  27. <artifactId>assertj-core</artifactId>
  28. <!-- use 2.9.1 for Java 7 projects -->
  29. <version>3.17.1</version>
  30. <scope>test</scope>
  31. </dependency>

 Mockito

Mockito 可以 mock 类的 public 方法或接口的方法。它是通过 cglib 动态生成一个 Proxy,因此在未指定某个方法行为的情况下,会默认返回空值,当然,一个完善的框架肯定会支持直接访问被代理的对象的真实方法的,下文会有介绍,一共会有3种方式哦,我们继续吧。

这里我们使用的 mock 类定义如下:

  1. import java.util.concurrent.TimeUnit;
  2. public class MockTarget {
  3. public void soSth() {
  4. System.out.println("do sth.");
  5. }
  6. public String sayHello() {
  7. return "Hello";
  8. }
  9. public String sayHello(String greetings) {
  10. return "Hello " + greetings;
  11. }
  12. public String callMethod(Object p) {
  13. return "callMethod " + p.toString();
  14. }
  15. public String callMethodWait(long million) {
  16. try {
  17. TimeUnit.MILLISECONDS.sleep(million);
  18. } catch (InterruptedException ignored) {
  19. }
  20. return "callMethod sleep " + million;
  21. }
  22. public Object callMethodWithException(Object p) {
  23. throw new IllegalStateException("测试异常");
  24. }
  25. }

when..then

用于 mock 方法调用的各种返回情况。

  • 通过 doCallRealMethod 指定 mock 对象的方法调用它的真实逻辑,也可通过 thenAnswer(Answers.CALLS_REAL_METHODS) 实现

  • 通过 when..thenThrow 或者 doThrow..when 的方式 mock 目标方法返回对应的异常

  • 通过 AssertJ 的句法 assertThatExceptionOfType..isThrownBy..withXxx断言某个方法的执行会抛出预期异常

  • anyXxx() 可用于表示任意类型的任意参数    

    • anyString() 代表任意字符串

    • anyInt() 代表任意int数值 

    • anyObject() 代表任意类型对象

  1. @Test
  2. public void testWhenAndThen() {
  3. MockTarget mock = Mockito.mock(MockTarget.class);
  4. when(mock.sayHello()).thenReturn("mock hello");
  5. assertEquals(mock.sayHello(), "mock hello");
  6. doCallRealMethod().when(mock).sayHello();
  7. assertEquals(mock.sayHello(), "Hello");
  8. when(mock.sayHello(anyString())).thenAnswer(Answers.CALLS_REAL_METHODS);
  9. assertEquals(mock.sayHello("testRun"), "Hello testRun");
  10. when(mock.callMethod(any())).thenReturn("mock return");
  11. assertEquals(mock.callMethod(new Object()), "mock return");
  12. when(mock.callMethodWithException(any())).thenThrow(new RuntimeException("mock throw exception"), new IllegalArgumentException("test illegal argument"));
  13. Assertions.assertThatExceptionOfType(RuntimeException.class)
  14. .isThrownBy(() -> mock.callMethodWithException("first invoke"))
  15. .withMessage("mock throw exception");
  16. Assertions.assertThatExceptionOfType(IllegalArgumentException.class)
  17. .isThrownBy(() -> mock.callMethodWithException("second invoke"))
  18. .withMessage("test illegal argument")
  19. .withNoCause();
  20. doAnswer((Answer<String>) invocation -> {
  21. Object[] args = invocation.getArguments();
  22. MockTarget mock1 = (MockTarget) invocation.getMock();
  23. return "mock sayHello " + args[0];
  24. }).when(mock).sayHello("doAnswer");
  25. assertEquals(mock.sayHello("doAnswer"), "mock sayHello doAnswer");
  26. // 1.doNothing, 2. throw RuntimeException
  27. doNothing().doThrow(RuntimeException.class).when(mock).soSth();
  28. mock.soSth();
  29. Assertions.assertThatExceptionOfType(RuntimeException.class).isThrownBy(mock::soSth);
  30. }

verify

用于验证某个方法是否被调用,包括可以验证该方法被调用的次数,以及等待异步方法调用完成等特性。

常用句式  verify(mockObject  [,  times(n)  ]  ).targetMethod

  1. @Test
  2. public void testVerifyInteractions() {
  3. // mock creation
  4. List mockedList = mock(List.class);
  5. mockedList.clear();
  6. // only clear() invoked
  7. verify(mockedList, only()).clear();
  8. verifyNoMoreInteractions(mockedList);
  9. // 此处不会抛异常,因为是mock的list对象,非实际list对象
  10. when(mockedList.get(1)).thenReturn("two");
  11. assertEquals(mockedList.get(1), "two");
  12. // using mock object - it does not throw any "unexpected interaction" exception
  13. mockedList.add("one");
  14. // selective, explicit, highly readable verification
  15. verify(mockedList).add("one");
  16. verify(mockedList, times(1)).clear();
  17. verify(mockedList, atLeastOnce()).add("one");
  18. verify(mockedList, atMostOnce()).add("one");
  19. verify(mockedList, atMost(1)).add("one");
  20. verify(mockedList, atLeast(1)).add("one");
  21. verify(mockedList, never()).add("never");
  22. }

verify 之 after 与 timeout

针对异步调用,我们可以通过 after 或 timeout 等待一定时间,来校验目标方法是否有调用,以及在此之后获取目标方法的返回值,作进一步逻辑校验

  • after 会阻塞等满时间之后再往下执行,是固定等待多长时间的语义

  • timeout 在等待期内,拿到结果后立即向下执行,不做多余等待;是最多等待多长时间的语义

  1. @Test
  2. public void testAfterAndTimeout() throws Exception {
  3. MockTarget mock = mockTarget;
  4. doCallRealMethod().when(mock).callMethodWait(anyLong());
  5. final long timeout = 500L;
  6. final long delta = 100L;
  7. // 异步调用
  8. CompletableFuture<Void> async = CompletableFuture.runAsync(() -> {
  9. try {
  10. TimeUnit.MILLISECONDS.sleep(timeout);
  11. } catch (InterruptedException ignored) {
  12. }
  13. mock.sayHello();
  14. mock.callMethod("test");
  15. mock.callMethod("test");
  16. });
  17. // timeout() exits immediately with success when verification passes
  18. // verify(mock, description("invoke not yet, This will print on failure")).callMethod("test");
  19. verify(mock, timeout(timeout + delta).times(2)).callMethod("test");
  20. // immediately success
  21. verify(mock, timeout(10)).sayHello();
  22. async.get();
  23. // after() awaits full duration to check if verification passes
  24. verify(mock, after(10).times(2)).callMethod("test");
  25. verify(mock, after(10)).sayHello();
  26. }
spy

spy 的官方定义是:

partial mocking, real methods are invoked but still can be verified and stubbed

会调用被 spy 的真实对象的方法,但仍能被 Mockiton 所直接用于 mock 和 verify,也就是说在没有配置 mock 行为的情况下默认是调用被 mock 对象的真实方法。

  • 句式 doXxx..when 当同一目标方法上定义了多个 mock 行为,后序 mock 可以覆盖前序 mock

  • clearInvocations 仅清理之前的调用

  • reset 会重置为初始状态(所有中途的赋值都会被清理掉)

  1. @Test
  2. public void testDoReturn() {
  3. // real creation
  4. List list = new LinkedList();
  5. List spy = spy(list);
  6. //optionally, you can stub out some methods:
  7. int mockSize = 100;
  8. when(spy.size()).thenReturn(mockSize);
  9. //size() method was stubbed - 100 is printed
  10. assertEquals(spy.size(), mockSize);
  11. // Overriding a previous exception-stubbing:
  12. when(spy.size()).thenThrow(new IllegalStateException("not init"));
  13. doReturn(mockSize).when(spy).size();
  14. assertEquals(spy.size(), mockSize);
  15. //Impossible: real method is called so spy.get(0) throws IndexOutOfBoundsException (the list is yet empty)
  16. Assertions.assertThatExceptionOfType(IndexOutOfBoundsException.class).isThrownBy(() -> spy.get(0));
  17. doReturn("mock data").when(spy).get(1);
  18. //using the spy calls real methods
  19. spy.add("one");
  20. assertEquals(spy.get(0), "one");
  21. /*
  22. Use this method in order to only clear invocations, when stubbing is non-trivial. Use-cases can be:
  23. You are using a dependency injection framework to inject your mocks.
  24. The mock is used in a stateful scenario. For example a class is Singleton which depends on your mock.
  25. Try to avoid this method at all costs. Only clear invocations if you are unable to efficiently test your program.
  26. */
  27. clearInvocations(spy);
  28. verify(spy, times(0)).add("two");
  29. reset(spy);
  30. when(spy.size()).thenReturn(0);
  31. assertEquals(spy.size(), 0);
  32. }


 PowerMock


以上介绍的是 Mockiton 中常用的API,而 PowerMock 则更强大,可以 mock static 方法,private 方法,final 方法,enum,构造函数调用等。

示例代码中用到的测试类如下:

  1. public enum TypeEnum {
  2. Y("TRUE"),
  3. N("FALSE");
  4. private final String title;
  5. TypeEnum(String title) {
  6. this.title = title;
  7. }
  8. public String getTitle() {
  9. return title;
  10. }
  11. }
  12. public final class FinalTarget {
  13. public FinalTarget() { }
  14. public final String finalMethod() {
  15. return "Hello final!";
  16. }
  17. }
  18. public class StaticTarget {
  19. public static String firstMethod(String name) {
  20. return "Hello " + name + " !";
  21. }
  22. public static String secondMethod() {
  23. return "Hello no one!";
  24. }
  25. }
  26. public class PartialTarget {
  27. private String arg;
  28. public PartialTarget(String arg) {
  29. this.arg = arg;
  30. }
  31. public PartialTarget() { }
  32. public String getArg() {
  33. return arg;
  34. }
  35. private String privateWithArg(String arg) {
  36. return "Hello privateWithArg! " + arg;
  37. }
  38. public String privateMethodCaller(String arg) {
  39. return privateWithArg(arg) + " privateMethodCall.";
  40. }
  41. }
类注解

在使用 PowerMockito mock static , private , final , enum , constructor 之前需要在测试类上加入如下注解:

  1. @RunWith(PowerMockRunner.class)
  2. @PrepareForTest({StaticTarget.class, PartialTarget.class, TypeEnum.class, FinalTarget.class})

static

PowerMockito.mockStatic 声明了要 mock static 方法的类

  1. PowerMockito.mockStatic(StaticTarget.class);
  2. StaticTarget.firstMethod("xxx");

verify

值得注意的是,它的 verify 方法使用比 Mockiton 更复杂。

需要先声明一下验证目标类的静态方法再紧接着调用一下,表示待验证的目标方法

  1. PowerMockito.verifyStatic(StaticTarget.class); // 1
  2. StaticTarget.firstMethod(invokeParam); // 2

也有类似于 Mockiton 的调用次数校验:

  1. PowerMockito.verifyStatic(StaticTarget.class, times(1));
  2. PowerMockito.verifyStatic(StaticTarget.class, Mockito.atLeastOnce());
private

PowerMock 模拟 private 方法 "privateWithArg" 的返回值并校验 "privateWithArg" 被调用的次数

  1. PartialTarget partialMock = PowerMockito.mock(PartialTarget.class);
  2. doCallRealMethod().when(partialMock).privateMethodCaller(anyString());
  3. PowerMockito.doReturn("mockResult").when(partialMock, "privateWithArg", any());
  4. // *privateMethodCaller* will invoke method *privateWithArg*
  5. String result = partialMock.privateMethodCaller("arg");
  6. Assert.assertEquals(result, "mockResult privateMethodCall.");
  7. PowerMockito.verifyPrivate(partialMock, times(1)).invoke("privateWithArg", "arg");

final

PowerMock 校验 mock final方法

  1. FinalTarget finalTarget = PowerMockito.mock(FinalTarget.class);
  2. String finalReturn = "finalReturn";
  3. PowerMockito.when(finalTarget.finalMethod()).thenReturn(finalReturn);
  4. Assert.assertThat(finalTarget.finalMethod(), is(finalReturn));

enum

PowerMock mock enum,这里的 Whitebox.setInternalState 可以设置 TypeEnum fieldName=N 的值为给定的 mock 枚举

  1. String mockValue = "mock title";
  2. TypeEnum typeMock = PowerMockito.mock(TypeEnum.class);
  3. Whitebox.setInternalState(TypeEnum.class, "N", typeMock);
  4. when(typeMock.getTitle()).thenReturn(mockValue);
  5. Assert.assertEquals(TypeEnum.N.getTitle(), mockValue);
  6. Assert.assertEquals(TypeEnum.Y.getTitle(), "TRUE");

constructor

构造器 mock 与 verify

  1. String arg = "special arg";
  2. PartialTarget partialWithArgSpy = PowerMockito.spy(new PartialTarget(arg));
  3. whenNew(PartialTarget.class).withNoArguments().thenReturn(partialWithArgSpy);
  4. PartialTarget partialNoArg = new PartialTarget();
  5. Assert.assertEquals(partialNoArg.getArg(), arg);
  6. verifyNew(PartialTarget.class).withNoArguments();

完整示例如下:

  1. import org.assertj.core.api.Assertions;
  2. import org.junit.Assert;
  3. import org.junit.Test;
  4. import org.junit.runner.RunWith;
  5. import org.mockito.Mockito;
  6. import org.powermock.api.mockito.PowerMockito;
  7. import org.powermock.core.classloader.annotations.PrepareForTest;
  8. import org.powermock.modules.junit4.PowerMockRunner;
  9. import org.powermock.reflect.Whitebox;
  10. import static org.hamcrest.core.Is.is;
  11. import static org.mockito.ArgumentMatchers.anyString;
  12. import static org.mockito.Mockito.times;
  13. import static org.powermock.api.mockito.PowerMockito.doCallRealMethod;
  14. import static org.powermock.api.mockito.PowerMockito.verifyNew;
  15. import static org.powermock.api.mockito.PowerMockito.when;
  16. import static org.powermock.api.mockito.PowerMockito.whenNew;
  17. @RunWith(PowerMockRunner.class)
  18. @PrepareForTest({StaticTarget.class, PartialTarget.class, TypeEnum.class, FinalTarget.class})
  19. public class PowerMockTest {
  20. @Test
  21. public void testStatic() throws Exception {
  22. PowerMockito.mockStatic(StaticTarget.class);
  23. String mockResult = "Static mock";
  24. PowerMockito.when(StaticTarget.firstMethod(anyString())).thenReturn(mockResult);
  25. String invokeParam = "any String parameter";
  26. Assert.assertEquals(StaticTarget.firstMethod(invokeParam), mockResult);
  27. // Verification of a static method is done in two steps.
  28. PowerMockito.verifyStatic(StaticTarget.class); // 1
  29. // StaticTarget.secondMethod();// not invoked
  30. StaticTarget.firstMethod(invokeParam);// 2
  31. // use argument matchers
  32. PowerMockito.verifyStatic(StaticTarget.class); // 1
  33. StaticTarget.firstMethod(anyString()); // 2
  34. // atLeastOnce
  35. PowerMockito.verifyStatic(StaticTarget.class, Mockito.atLeastOnce()); // 1
  36. StaticTarget.firstMethod(anyString()); // 2
  37. // times
  38. PowerMockito.verifyStatic(StaticTarget.class, times(1)); // 1
  39. StaticTarget.firstMethod(anyString()); // 2
  40. // partial mocking of a private method & verifyPrivate
  41. // PartialTarget partialNoArgSpy = PowerMockito.spy(new PartialTarget());
  42. PartialTarget partialMock = PowerMockito.mock(PartialTarget.class);
  43. doCallRealMethod().when(partialMock, "privateMethodCaller", anyString());
  44. PowerMockito.doReturn("mockResult").when(partialMock, "privateWithArg", any());
  45. // *privateMethodCaller* will invoke method *privateWithArg*
  46. String result = partialMock.privateMethodCaller("arg");
  47. Assert.assertEquals(result, "mockResult privateMethodCall.");
  48. PowerMockito.verifyPrivate(partialMock, times(1)).invoke("privateWithArg", "arg");
  49. // Final
  50. FinalTarget finalTarget = PowerMockito.mock(FinalTarget.class);
  51. String finalReturn = "finalReturn";
  52. PowerMockito.when(finalTarget.finalMethod()).thenReturn(finalReturn);
  53. Assert.assertThat(finalTarget.finalMethod(), is(finalReturn));
  54. // enum
  55. String mockValue = "mock title";
  56. TypeEnum typeMock = PowerMockito.mock(TypeEnum.class);
  57. Whitebox.setInternalState(TypeEnum.class, "N", typeMock);
  58. when(typeMock.getTitle()).thenReturn(mockValue);
  59. Assert.assertEquals(TypeEnum.N.getTitle(), mockValue);
  60. Assert.assertEquals(TypeEnum.Y.getTitle(), "TRUE");
  61. // verify New
  62. String arg = "special arg";
  63. PartialTarget partialWithArgSpy = PowerMockito.spy(new PartialTarget(arg));
  64. whenNew(PartialTarget.class).withNoArguments().thenReturn(partialWithArgSpy);
  65. PartialTarget partialNoArg = new PartialTarget();
  66. Assert.assertEquals(partialNoArg.getArg(), arg);
  67. verifyNew(PartialTarget.class).withNoArguments();
  68. // throw exception
  69. PowerMockito.doThrow(new ArrayStoreException("Mock secondMethod error")).when(StaticTarget.class);
  70. StaticTarget.secondMethod();
  71. // AssertJ: Exception assertions
  72. Assertions.assertThatThrownBy(StaticTarget::secondMethod)
  73. .isInstanceOf(ArrayStoreException.class)
  74. .hasNoCause()
  75. .hasMessage("Mock secondMethod error");
  76. }
  77. }

 AssertJ

上面提到的 AssertJ 是 Assert 的一些功能增强,以流式编程的方式调用,下面介绍一些常用的用法

  • isIn,isNotIn 和 matches 用于断言匹配条件

  • filteredOn 可以针对 assertThat 中传入的参数进行过滤,类似 java8 中Stream() 的 filter 方法

  • extracting 可以针对 assertThat 中传入的元组进行字段提取校验

  • assertThatExceptionOfType 和 assertThatThrownBy 可用于捕获预期的异常

为了方便使用,AssertJ 还提供了几种常用的异常断言的包装器:

  1. // AssertJ provides wrappers for common exception types
  2. Assertions.assertThatNoException();
  3. Assertions.assertThatIOException();
  4. Assertions.assertThatNullPointerException();
  5. Assertions.assertThatIllegalStateException();
  6. Assertions.assertThatIllegalArgumentException();

示例如下:

  1. import org.assertj.core.api.Assertions;
  2. import org.junit.Test;
  3. import java.util.Arrays;
  4. import java.util.List;
  5. import static org.assertj.core.api.Assertions.tuple;
  6. public class AssertTest {
  7. @Test
  8. public void testAssertJ() {
  9. String title = "foo";
  10. AssertTarget assertTarget = new AssertTarget(title, 12, TypeEnum.Y);
  11. String msg = "Illegal Argument error";
  12. Exception cause = new NullPointerException("cause exception msg");
  13. Assertions.assertThatExceptionOfType(IllegalArgumentException.class)
  14. .isThrownBy(() -> assertTarget.throwIllegalArgumentException(msg, cause))
  15. .withMessage(msg)
  16. .withMessageContaining("Argument error")
  17. .overridingErrorMessage("new error message")
  18. .withCause(cause);
  19. Assertions.assertThatThrownBy(() -> assertTarget.throwIllegalArgumentException(msg, cause))
  20. .isInstanceOf(IllegalArgumentException.class)
  21. .hasMessageContaining("Argument error");
  22. Assertions.assertThat(assertTarget.getTitle())
  23. // as() is used to describe the test and will be shown before the error message
  24. .as("PartialTarget's arg is not match", assertTarget.getTitle())
  25. .startsWith(title)
  26. .endsWith(title)
  27. .contains(title)
  28. .isNotEqualTo("foo bar")
  29. .isEqualToIgnoringCase("FOO")
  30. .isEqualTo(title);
  31. AssertTarget target1 = new AssertTarget("testTitle", 12, TypeEnum.N);
  32. AssertTarget target2 = new AssertTarget("titleVal1", 16, TypeEnum.N);
  33. AssertTarget target3 = new AssertTarget("titleVal2", 18, TypeEnum.Y);
  34. AssertTarget target4 = new AssertTarget("titleVal3", 20, TypeEnum.N);
  35. List<AssertTarget> assertTargetRing = Arrays.asList(target1, target2, target3);
  36. Assertions.assertThat(target1.getNum()).withFailMessage("the num not matches").isEqualTo(12);
  37. Assertions.assertThat(target1.getType().equals(TypeEnum.N)).isTrue();
  38. Assertions.assertThat(target1).isIn(assertTargetRing);
  39. Assertions.assertThat(target4).isNotIn(assertTargetRing);
  40. Assertions.assertThat(target4).matches(e -> e.getNum() > 18 && e.getType().equals(TypeEnum.N));
  41. Assertions.assertThat(assertTargetRing)
  42. // extracting multiple values at once grouped in tuples
  43. .extracting("num", "type.title")
  44. .contains(tuple(16, TypeEnum.N.getTitle())
  45. , tuple(18, TypeEnum.Y.getTitle()));
  46. Assertions.assertThat(assertTargetRing)
  47. // filtering a collection before asserting
  48. .filteredOn(e -> e.getTitle().startsWith("title"))
  49. .extracting(AssertTarget::getNum)
  50. .contains(16, 18);
  51. }
  52. }


真香


以上针对自己使用的 mock 单元测试的三板斧 Mockito + PowerMock + AssertJ 常用姿势做了小结。

  • 利用 Mockiton 做常规类和接口的 mock

  • PowerMock 则可以 mock 静态方法,私有方法,final 方法,枚举,构造函数等

  • AssertJ 流式风格,增强 assert 判断逻辑和校验异常流程

更多姿势等待大家在实操中继续解锁,利用这些姿势在后续的开发自测中可以更快速的做自我逻辑验证,而我再也不必等待每次项目开发环境的 10min 部署了。

艾玛,真香~

写在最后


最后的结尾,介绍一下我们团队吧,最近团队内新增了一条业务线,HC多多,机会多多,欢迎内转和外部投递:

淘系技术部-商家运营

我们是淘系商家运营中台团队,负责淘系千万级商家运营技术体系构建,在这里,你将负责攻克商家域复杂业务模型、海量数据挖掘、高稳定性等带来的技术难题与挑战,打造全市场商家运营技术架构标杆,驱动市场增量价值,并成为最懂商业的技术人。

我们HC不限,可以直接跟老板聊,招聘流程快。期待你的加入,共建To B端最具代表性的商业技术体系,迎接产业互联网的到来。

地点杭州阿里巴巴西溪园区。欢迎各路大侠加入!

简历投递邮箱????:xzc270316@alibaba-inc.com

参考文档:


Mockito: https://site.mockito.org

PowerMock: https://powermock.github.io
AssertJ: https://assertj.github.io/doc

✿  拓展阅读

作者|谢志春(志春)

编辑|橙子君

出品|阿里巴巴新零售淘系技术

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/450331
推荐阅读
相关标签
  

闽ICP备14008679号