java:单元测试(UT)

原则
1、分析需求场景
2、依据场景列出判断分支
3、依据分支编写测试用例,若方法具有多层调用,则依据逻辑,判断入参变化点,针对具有入参变化点的方法编写UT
4、编写实现逻辑

框架
本次使用Junit进行测试,基于Junit的模拟框架使用mockito配合powermock
Junit:每个测试都是一个方法,比较输入输出以实现代码验证
TestNG:测试被组织的类,与Junit相同,但是要运行TestNG,必须添加配置
基于Junit模拟框架:mockito、easymock、powermock

基础知识:(待补充。。。)
Mockito
Powermock

mock与spy的区别:
mock:类中所有的方法均被置空,均返回null,不真实执行方法内部逻辑
spy:类中所有方法均真实执行

下面将针对写单测时遇到的问题有针对性列举
1、针对类A方法doIt进行单测时,调用了其中的public static void getResult(String s),而且getResult中调用了工具类的private static int empty(String s)方法
目标是执行getResult进入方法内执行,而empty不真实执行
解决方法:
1.1首先分析
工具类需要@PrepareForTest进行声明;
static方法需要使用PowerMock进行mock;
进入类A的方法中真正执行,需要间谍spy
某些方法不真正执行,使用doReturn方式
1.2实战
被测类:

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

public class A{
    private static int empty() {
        System.out.println("enter empty");
        return 1;
    }

    public static int getResult() {
        System.out.println("realprint");
        int a = empty();
        System.out.println(a);
        return 1;
    }

    public static int doIt() {
        return SumTwo.getResult();
    }
}

测试类:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import program.SumTwo;

@RunWith(PowerMockRunner.class)
@PrepareForTest(A.class)
public class PowermockTest {
    @Test
    public void sumtwoTest() throws Exception {
        PowerMockito.mockStatic(A.class);
        PowerMockito.spy(A.class);

        PowerMockito.doReturn(2).when(A.class, "empty");

        int result = A.doIt();
    }
}

该测试用例的运行结果符合预期,empty方法不执行,因此不会打印“123”
打印结果如下:

realprint
2

2、when中调用了类的方法,方法所在类必须先在prepare中声明出处,并且需要提前mock,若类中该方法为static则需要对该类用mockstatic,以便执行该方法
eg:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import program.SumTwo;

@RunWith(PowerMockRunner.class)
@PrepareForTest(SumTwo.class)
public class PowermockTest {
    @Test
    public void sumtwoTest() throws Exception {
        PowerMockito.mockStatic(SumTwo.class);

        PowerMockito.doReturn(2).when(SumTwo.class, "empty");

        int result = SumTwo.doIt();
    }
}

如上测试代码可以正常运行
当我们将mockstatic修改为mock,会出现如下报错:

java.lang.NullPointerException
	at org.powermock.api.mockito.internal.expectation.PowerMockitoStubberImpl.addAnswersForStubbing(PowerMockitoStubberImpl.java:68)
	at org.powermock.api.mockito.internal.expectation.PowerMockitoStubberImpl.when(PowerMockitoStubberImpl.java:43)
	at org.powermock.api.mockito.internal.expectation.PowerMockitoStubberImpl.when(PowerMockitoStubberImpl.java:111)
	at PowermockTest.sumtwoTest(PowermockTest.java:17)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.internal.runners.TestMethod.invoke(TestMethod.java:68)

3、对例1的扩展,模拟执行的private方法带入参
注意,若不想真正执行该private方法,除了使用doReturn外,需要模拟传入的入参与代码真正执行时的入参一致,否则仍然会执行该private方法。

import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import program.SumTwo;

import static org.mockito.Matchers.any;

@RunWith(PowerMockRunner.class)
@PrepareForTest(SumTwo.class)
public class PowermockTest {
    @Test
    public void sumtwoTest() throws Exception {
        PowerMockito.spy(SumTwo.class);
        PowerMockito.doReturn(2).when(SumTwo.class, "empty", 3);
		int result = SumTwo.doIt();
    }
}

运行上述调用代码,执行后,打印结果如下

realprint
enter empty
1

若我们修改empty的入参,为真实入参1则会出现如下打印结果

import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;

import program.SumTwo;

import static org.mockito.Matchers.any;

@RunWith(PowerMockRunner.class)
@PrepareForTest(SumTwo.class)
public class PowermockTest {
    @Test
    public void sumtwoTest() throws Exception {
        PowerMockito.spy(SumTwo.class);
        PowerMockito.doReturn(2).when(SumTwo.class, "empty", 1);
		int result = SumTwo.doIt();
    }
}
realprint
2

4、当对象创建在方法中进行,对该方法进行UT时,需要通过PowerMock的whenNew方法创建该对象,针对该对象进行打桩。
PowerMockito.whenNew(A.class).withArguments(arg1, arg2,… argn).thenReturn(result);

5、针对ENUM类型的单例,做UT时,使用白盒

public enum SingletonObject {
    INSTANCE;
    private int num;

    protected void setNum(int num) {
        this.num = num;
    }

    public int getNum() {
        return num;
    }
}

单例调用:

public class SingletonConsumer {
    public String consumeSingletonObject() {
        return String.valueOf(SingletonObject.INSTANCE.getNum());
    }
}

UT:

import static org.junit.Assert.*;
import static org.powermock.api.mockito.PowerMockito.*;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import org.powermock.reflect.Whitebox;

@RunWith(PowerMockRunner.class)
@PrepareForTest({SingletonObject.class})
public class SingletonConsumerTest {
    @Test
    public void testConsumeSingletonObject() throws Exception {
    	**// 真实执行单例类的某个方法使用spy,若不执行,使用mock(SingletonObject.class)**
        SingletonObject mockInstance = spy(SingletonObject.INSTANCE);
        Whitebox.setInternalState(SingletonObject.class, "INSTANCE", mockInstance);

        when(mockInstance.getNum()).thenReturn(42);

        assertEquals("42", new SingletonConsumer().consumeSingletonObject());
    }
}
  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值