Java如何写单元测试

1. JUnit 基本测试类

class StandardTests {

    @BeforeAll
    static void initAll() {
    }

    @BeforeEach
    void init() {
    }

    @Test
    void succeedingTest() {
    }

    @Test
    @Timeout(value = 100, unit = TimeUnit.MILLISECONDS)
    void failingTest() {
        fail("a failing test");
    }

    @Test
    @Disabled("for demonstration purposes")
    void skippedTest() {
        // not executed
    }

    @Test
    void abortedTest() {
        assumeTrue("abc".contains("Z"));
        fail("test should have been aborted");
    }

    @AfterEach
    void tearDown() {
    }

    @AfterAll
    static void tearDownAll() {
    }

}

2.Mockito

Mockito是一个模拟框架,可以按需求控制方法被调用时的返回值或抛出异常操作,还能够对mock对象的操作进行验证。

2.1 简单demo:

LinkedList mockedList = mock(LinkedList.class);		//模拟

when(mockedList.get(0)).thenReturn("first");		//存根
when(mockedList.get(1)).thenThrow(new RuntimeException());

System.out.println(mockedList.get(0));
System.out.println(mockedList.get(1));

verify(mockedList).get(0);		//验证

2.2 mock()如何实现的

创建一个Mock对象:

LinkedList mockedList = mock(LinkedList.class);

如果调用mockedList.isEmpty()会返回什么?

//创建Answer
Object returnValueFor(Class<?> type) {
    if (Primitives.isPrimitiveOrWrapper(type)) {
        return Primitives.defaultValueForPrimitiveOrWrapper(type);
        //new instances are used instead of Collections.emptyList(), etc.
        //to avoid UnsupportedOperationException if code under test modifies returned collection
    } else if (type == Collection.class) {
        return new LinkedList<Object>();
    } else if (type == Set.class) {
        return new HashSet<Object>();
    } else if (type == HashSet.class) {
        return new HashSet<Object>();
    } else if (type == SortedSet.class) {
        return new TreeSet<Object>();
    } else if (type == TreeSet.class) {
        return new TreeSet<Object>();
    } else if (type == LinkedHashSet.class) {
        return new LinkedHashSet<Object>();
    } else if (type == List.class) {
        return new LinkedList<Object>();
    } else if (type == LinkedList.class) {
        return new LinkedList<Object>();
    } else if (type == ArrayList.class) {
        return new ArrayList<Object>();
    } else if (type == Map.class) {
        return new HashMap<Object, Object>();
    } else if (type == HashMap.class) {
        return new HashMap<Object, Object>();
    } else if (type == SortedMap.class) {
        return new TreeMap<Object, Object>();
    } else if (type == TreeMap.class) {
        return new TreeMap<Object, Object>();
    } else if (type == LinkedHashMap.class) {
        return new LinkedHashMap<Object, Object>();
    }
    // TODO return empty Iterable ; see issue 175

    //Let's not care about the rest of collections.
    return null;
}

//基本数据类型默认值
static {
    PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(Boolean.class, false);
    PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(Character.class, '\u0000');
    PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(Byte.class, (byte) 0);
    PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(Short.class, (short) 0);
    PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(Integer.class, 0);
    PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(Long.class, 0L);
    PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(Float.class, 0F);
    PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(Double.class, 0D);

    PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(boolean.class, false);
    PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(char.class, '\u0000');
    PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(byte.class, (byte) 0);
    PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(short.class, (short) 0);
    PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(int.class, 0);
    PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(long.class, 0L);
    PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(float.class, 0F);
    PRIMITIVE_OR_WRAPPER_DEFAULT_VALUES.put(double.class, 0D);
}


//创建代理对象
public <T> T imposterise(final MethodInterceptor interceptor, Class<T> mockedType, Class<?>... ancillaryTypes) {
    Class<Factory> proxyClass = null;
    Object proxyInstance = null;
    try {
        setConstructorsAccessible(mockedType, true);
        proxyClass = createProxyClass(mockedType, ancillaryTypes);
        proxyInstance = createProxy(proxyClass, interceptor);
        return mockedType.cast(proxyInstance);
    } catch (ClassCastException cce) {
        throw new MockitoException(join(
            "ClassCastException occurred while creating the mockito proxy :",
            "  class to mock : " + describeClass(mockedType),
            "  created class : " + describeClass(proxyClass),
            "  proxy instance class : " + describeClass(proxyInstance),
            "  instance creation by : " + instantiator.getClass().getSimpleName(),
            "",
            "You might experience classloading issues, disabling the Objenesis cache *might* help (see MockitoConfiguration)"
        ), cce);
    } finally {
        setConstructorsAccessible(mockedType, false);
    }
}

2.3 spy半模拟

mock只能mock Class或者Interface,如果想mock一个实例或类进行部分mock则可以使用spy

@Test
public void test1() {
    List list = new LinkedList();
    list.add("a");

    List spy = spy(list);
    doReturn("b").when(spy).get(2);

    System.out.println(spy.get(0));
    System.out.println(spy.get(2));
}

mock和spy有什么区别?

static class A {
    int function1() {
        System.out.println("this is function 1");
        return 1;
    }

    int function2() {
        System.out.println("this is function 2");
        return 2;
    }
}


@Test
public void differenceBetweenMockAndSpy() {
    A mockA = mock(A.class);
    A spyA = spy(A.class);

    System.out.println(mockA.function1());
    System.out.println();
    System.out.println(spyA.function1());
}

在半模拟中,未指定的stub会自动执行callRealMethod()

2.4 存根方式区别

Mockito有两种存根语法,do...when...actionwhen...then,两种语法在mock全模拟情况下作用相同,但是在spy半模拟情况下效果不同:

static class A {
    int function1() {
        System.out.println("this is function 1");
        return 1;
    }

    int function2() {
        System.out.println("this is function 2");
        return 2;
    }
}

@Test
public void differenceBetweenWhenThenAndDoWhen() {
    A mockA = spy(A.class);

    when(mockA.function1()).thenReturn(99);
    doReturn(88).when(mockA).function2();

    System.out.println(mockA.function1());
    System.out.println();
    System.out.println(mockA.function2());
}

2.5 与SpringBoot结合

引入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>

测试类

@Autowired
private WebApplicationContext applicationContext;

//实现对容器Bean的mock
@MockBean 
private Service1 service1;

//创建mockMvc
@Before
public void setUp() throws Exception {
    mockMvc = MockMvcBuilders.webAppContextSetup(applicationContext).build();
}

@Test
public void test1(){
    //mock service1,如果/test1的controller调用了service1,则会返回999
    when(service1.query(any())).thenReturn(999);
    MvcResult mvcResult = mockMvc.perform(MockMvcRequestBuilders.post("/test1")
        .header("Date", dateStr)
        .contentType(MediaType.APPLICATION_JSON_UTF8)
        .content(requestBodyJson)
}