欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

testng + mockito + powerMock

程序员文章站 2024-03-15 19:37:48
...

单元测试

添加pom依赖

		<dependency>
			<groupId>org.testng</groupId>
			<artifactId>testng</artifactId>
			<version>6.8</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.mockito</groupId>
			<artifactId>mockito-all</artifactId>
			<version>1.10.19 </version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.powermock</groupId>
			<artifactId>powermock-api-mockito</artifactId>
			<version>1.6.5</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.powermock</groupId>
			<artifactId>powermock-module-testng</artifactId>
			<version>1.6.5</version>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.powermock</groupId>
			<artifactId>powermock-core</artifactId>
			<version>1.6.5</version>
			<scope>test</scope>
		</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

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

测试覆盖分析工具EclEmma安装

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

Test-NG简介

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

注解

@BeforeSuite 运行suite中所有测试之前执行。
@AfterSuite 运行suite中所有测试之后执行。
@BeforeClass 运行当前类中所有测试之前执行。
@AfterClass 运行当前类中所有测试之后执行。
@BeforeTest 运行<test>标签内的所有测试之前运行。
@AfterTest 运行<test>标签内的所有测试之后运行。
@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 学习资料