飞道的博客

单元测试实践篇:Mock

489人阅读  评论(0)

淘系的技术发展已经有相当一段历史了,在历史的长河中总能沉淀出很多复杂的巨型项目,包罗多个业务,而且往往服务依赖比较复杂;再加上一些特殊环境变量的设置,想要在本地运行、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

✿  拓展阅读

作者|谢志春(志春)

编辑|橙子君

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


转载:https://blog.csdn.net/Taobaojishu/article/details/108591311
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场