testng + mockito + powerMock

单元测试

添加pom依赖

		<dependency>
			<groupId>org.testnggroupId>
			<artifactId>testngartifactId>
			<version>6.8version>
			<scope>testscope>
		dependency>
		<dependency>
			<groupId>org.mockitogroupId>
			<artifactId>mockito-allartifactId>
			<version>1.10.19 version>
			<scope>testscope>
		dependency>
		<dependency>
			<groupId>org.powermockgroupId>
			<artifactId>powermock-api-mockitoartifactId>
			<version>1.6.5version>
			<scope>testscope>
		dependency>
		<dependency>
			<groupId>org.powermockgroupId>
			<artifactId>powermock-module-testngartifactId>
			<version>1.6.5version>
			<scope>testscope>
		dependency>
		<dependency>
			<groupId>org.powermockgroupId>
			<artifactId>powermock-coreartifactId>
			<version>1.6.5version>
			<scope>testscope>
		dependency>

插件安装(IDEA自带,直接跳过)

TestNG

  1. 下载Eclipse-TestNG插件地址: http://dl.bintray.com/testng-team/testng-eclipse-release/updatesites/ 选择一个版本下载,完成后将features和plugins内容分别复制到eclipse对应目录,重启验证安装
  2. 验证安装:
    在Preference输入test,看到
    testng + mockito + powerMock_第1张图片

或者,在任意项目右键运行可以看到(debug as 也有可用于调试)
testng + mockito + powerMock_第2张图片

测试覆盖分析工具EclEmma安装

  1. Eclipse 下载地址 下载安装
  2. 验证安装,在任意项目点右键打开菜单,能找到Coverage As
    testng + mockito + powerMock_第3张图片
    或在windows->Show View –>Other窗口,java菜单下能找到Coverage
    testng + mockito + powerMock_第4张图片

Test-NG简介

TestNG是一个设计用来简化广泛的测试需求的测试框架,提供从单元测试到集成测试的支持(以下来自官网介绍)

注解

@BeforeSuite 运行suite中所有测试之前执行。
@AfterSuite 运行suite中所有测试之后执行。
@BeforeClass 运行当前类中所有测试之前执行。
@AfterClass 运行当前类中所有测试之后执行。
@BeforeTest 运行标签内的所有测试之前运行。
@AfterTest 运行标签内的所有测试之后运行。
@BeforeGroups 组内所有测试之前执行。
@AfterGroups 组内所有测试之后执行。
@BeforeMethod 每个测试方法之前执行。
@AfterMethod 每个测试方法之后执行。
@DataProvider 提供数据的一个测试方法。注解的方法必须返回一个Object[] [],其中每个对象[]都可以做为测试方法的参数。
@Factory 作为一个工厂,返回TestNG的测试类的对象将被用于标记的方法。该方法必须返回Object[]
@Listeners 定义一个测试类的监听器。
@Parameters 介绍如何将参数传递给@Test方法。
@Test 标记一个类或方法作为测试的一部分。

Mockito详解

Mock(模拟,不真实的)测试就是在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法。

  • 不容易构造:如HttpServletRequest必须在Servlet容器中才能构造出来
  • 比较复杂的对象:如JDBC中的ResultSet对象

简单实例

import static org.mockito.Mockito.*;

import java.util.List;

import org.testng.Assert;
import org.testng.annotations.Test;

public class SimpleMockTest {
    @Test  
    public void testSimple(){  
          
        //创建mock对象,参数可以是类,也可以是接口  
        List<String> list = mock(List.class);  
          
        //设置方法的预期返回值  
        when(list.get(0)).thenReturn("hello mock");  
      
        String result = list.get(0);  
          
        //验证方法调用(是否调用了get(0))  
        verify(list).get(0);  
          
        //测试  
        Assert.assertEquals("hello mock", result);  
    }  
}

Stubbing & 连续Stubbing

Stubbing(打桩):人为介入,为Mock对象指定行为或行为发生的结果

@Test
    public void testMockStubbing() {
        // You can mock concrete classes, not just interfaces
        LinkedList mockedList = mock(LinkedList.class);

        // stubbing
        when(mockedList.get(0)).thenReturn("first");
        when(mockedList.get(1)).thenThrow(new RuntimeException());
        
        // 连续stubbing
	 	when(mockedList.get(0)).thenReturn("first").thenReturn("second").thenReturn("Third");

        // following prints "first"
        System.out.println(mockedList.get(0));

        // following throws runtime exception
        System.out.println(mockedList.get(1));

        // following prints "null" because get(999) was not stubbed
        // 默认情况下,对于所有有返回值且没有stub过的方法,mockito会返回相应的默认值。
        // 对于内置类型会返回默认值,如int会返回0,布尔值返回false。对于其他type会返回null。
        System.out.println(mockedList.get(999));

        // Although it is possible to verify a stubbed invocation, usually it's just redundant
        // If your code cares what get(0) returns, then something else breaks (often even before verify() gets
        // executed).
        // If your code doesn't care what get(0) returns, then it should not be stubbed. Not convinced? See here.
        verify(mockedList).get(0);

        // 重复stub两次,则以第二次为准。如下将返回"second":
        when(mockedList.get(0)).thenReturn("first");

        when(mockedList.get(0)).thenReturn("second");

        // 如果是下面这种形式,则表示第一次调用时返回“first”,第二次调用时返回“second”。可以写n多个.
        when(mockedList.get(0)).thenReturn("first").thenReturn("second");
        // 但是,如果实际调用的次数超过了stub过的次数,则会一直返回最后一次stub的值。
        // 如上例,第三次调用get(0)时,则会返回"second".
    }

参数匹配

	@Test
    public void testArgumentMatcher() {
        List mockedList = mock(List.class);
        // stubbing using built-in anyInt() argument matcher
        when(mockedList.get(anyInt())).thenReturn("element");

        // stubbing using custom matcher (let's say MyMatcher() returns your own matcher implementation):
		when(mockedList.contains(argThat(new MyMatcher()))).thenReturn(true);

        mockedList.contains("element");

        // following prints "element"
        System.out.println(mockedList.get(999));

        // you can also verify using an argument matcher
        verify(mockedList).get(anyInt());

        // argument matchers can also be written as Java 8 Lambdas
        // verify(mockedList).add(someString -> someString.length() > 5);

        File mock = mock(File.class); // 首先mock File类。
        // 注意new AnyFiles()并不是一个matcher,需要调用argThat(new IsAnyFiles()))才返回一个matcher。
        // 下句中stub:当调用renameTo方法时,返回false。该方法参数可以是任意file对象。
        when(mock.renameTo(argThat(new AnyFiles()))).thenReturn(false);
        mock.renameTo(new File("test"));

        // 下句verify renameTo方法被调用了一次,同时输入参数是任意file。
        verify(mock).renameTo(argThat(new AnyFiles()));
    }

    class MyMatcher extends ArgumentMatcher<Object> {
        public boolean matches(Object argument) {
		if (argument != null && "element".equals(argument.toString())) {
              	return true;
            }
            return false;
        }
    }

    class AnyFiles extends ArgumentMatcher<File> {
        public boolean matches(Object file) {
            return file.getClass() == File.class;
        }
}

其他ArgumentMatcher
除了anyInt()之外,还有很多可匹配的参数,如

  • anyBoolean()
  • isNull()
  • anyList()
  • booleanThat()
  • isA(): //Object argument that implements the given class.
  • eq(boolean value): //boolean argument that is equal to the given value
  • endsWith(String suffix)
  • same(T value)

详细资料

注意事项
如果在调用方法时需要传入多个参数,其中一个参数使用了argument matcher,那么所有的参数必须都是matcher。不可以matcher和实际的参数混着用

		verify(mock).someMethod(anyInt(), anyString(), eq("third argument"));
        //above is correct - eq() is also an argument matcher

        verify(mock).someMethod(anyInt(), anyString(), "third argument");
        //above is incorrect - exception will be thrown because third argument is given without an argument matcher

do…when形式的stubbing

doThrow(), doAnswer(), doNothing(), doReturn() and doCallRealMethod()

	@Test(expectedExceptions = RuntimeException.class)
    public void testDoWhen() {
        List mockedList = mock(List.class);
        doThrow(new RuntimeException()).when(mockedList).clear();

        // following throws RuntimeException:
        mockedList.clear();
 }

Stubing抛出异常

	@Test(expectedExceptions = RuntimeException.class)
    public void testThrowException(){
        List mockedList = mock(List.class);
        doThrow(new RuntimeException()).when(mockedList).clear();

        //following throws RuntimeException:
        mockedList.clear();
}

PowerMock 实例

模拟void方法、静态方法、私有方法、final方法、屏蔽静态块运行、屏蔽类加载器加载特定类,忽略异常(PowerMockIgnore),使用构造方法Mock对象(whenNew)

  • 待覆盖类
package xxx;

import java.util.Properties;

import javax.servlet.http.HttpServletRequest;

import xxx.dao.CodeDao;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

public class SimpleClass {

    private static final Logger LOGGER = LoggerFactory.getLogger(CustLevelServiceImpl.class);

    private HttpServletRequest httpServletRequest;

    @Autowired
    private CodeDao codeDao;


    /**
     * 调用dao层处理业务
     *
     * @param code key
     * @return code实体toString
     */
    public String queryCodeString(String code) throws Exception {
        // mock httpServletRequest,模拟返回
        String req = httpServletRequest.getParameter(code);
        // 2个分支都需要覆盖
        if (req == null || req.length() == 0) {
            return null;
        }

        // mock 带有返回值的静态方法
        String configValue = PropertyUtil.getConfig("1", "1");
        if ("1".equals(configValue)) {
            return "0000";
        }

        // mock codeStringDao,模拟返回
        String value = codeDao.queryCode(code, code).toString();
        // mock void返回值的静态方法
        try {
            PropertyUtil.setConfig(code, value);
        } catch (Exception e) {
            throw e;
        }

        return value;
    }
}
  • 工具类
package xxx;

public class PropertyUtil {

    /**
     * 静态块
     */
    static {

        System.out.println("do some init");
        // 阻止其运行
        System.out.println(1 / 0);
    }

    public static String getConfig(String key, String defaultValue) {
        return "1";
    }

    public static void setConfig(String code, String value) throws Exception {
        if (code == null) {
            throw new Exception();
        }
    }
}

  • 单元测试类
package xxx;

import java.util.Properties;

import javax.servlet.http.HttpServletRequest;

import xxx.dao.CodeDao;
import xxxx.CodeEntity;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.core.classloader.annotations.SuppressStaticInitializationFor;
import org.testng.IObjectFactory;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.ObjectFactory;
import org.testng.annotations.Test;

import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.when;

@PrepareForTest(PropertyUtil.class)
@SuppressStaticInitializationFor("xxx.PropertyUtil")
@PowerMockIgnore("java.lang.*")
public class MockTest {

    @InjectMocks
    SimpleClass simpleClass;

    @Mock
    CodeDao codeDao;

    @Mock
    HttpServletRequest httpServletRequest;

    @Mock
    Properties properties;

    @BeforeClass
    public void beforeClass() {
        MockitoAnnotations.initMocks(this);
    }

    @ObjectFactory
    public IObjectFactory getObjectFactory() {
        return new org.powermock.modules.testng.PowerMockObjectFactory();
    }

    @Test
    public void testQueryCode() {

        // mock httpServletRequest  获取参数
        when(httpServletRequest.getParameter(anyString())).thenReturn(null).thenReturn("req");

        // mock静态方法,有返回值
        PowerMockito.mockStatic(PropertyUtil.class);
        PowerMockito.when(PropertyUtil.getConfig(eq("1"), eq("1"))).thenReturn("1").thenReturn("0");
      
        // mock dao,模拟数据库返回
        when(codeDao.queryCode(anyString(), anyString())).thenReturn(new CodeEntity());
        // mock静态方法,无返回值
        try {
            PowerMockito.doNothing()
                    .when(PropertyUtil.class, "setConfig", anyString(), anyString());
        } catch (Exception e) {
            // 忽略
        }

        // return null
        try {
            simpleClass.queryCodeString("code");
        } catch (Exception e) {
        }
        // return "0000"
        try {
            simpleClass.queryCodeString("code");
        } catch (Exception e) {
        }
        // return null
        try {
            simpleClass.queryCodeString("code");
        } catch (Exception e) {
        }
        // return value
        try {
            simpleClass.queryCodeString("code");
        } catch (Exception e) {
        }
    }

    @Test
    public void testQueryCodeStringExp() {
        PowerMockito.mockStatic(PropertyUtil.class);
        // mock静态方法,抛出异常
        try {
            PowerMockito.doThrow(new RuntimeException())
                    .when(PropertyUtil.class, "setConfig", anyString(), anyString());
        } catch (Exception e) {
            e.printStackTrace();
        }

        // return null runtimeException
        try {
            simpleClass.queryCodeString("code");
        } catch (Exception e) {
        }
    }
}

发散

  • 抽象类中方法覆盖
    可以写一个子类继承抽象类,然后写子类的单测,从而覆盖父类
  • 有些变量无法mock
    通过反射重置变量,再mock
  • 大量实体类的getter\setter方法
    通过反射,扫描包路径,自动调用

计划

生成单测的自动化工具

参考

TestNG官网
Mockito官网
Power GitHub源码地址
IBM 学习资料

你可能感兴趣的:(java)