mock powermock

Mock 测试实际运用场景

  • 解决前后端依赖,或者 架构中每一层之间的依赖
  • 解决外部接口依赖
  • 当需要调用某层API并且这个API现在还不可用

为什么会有mock

TDD:测试驱动开发,其思想为根据需求抽象接口,在编写测试用例,然后再开始编写开发代码。在TDD开发模式中总是先写单元测试模块,之后再进行开发。
DDD:领域驱动开发

创建一个虚假的对象。

常用注解

    // 修饰被Mock的类,Mock对象自动注入
    // 快速的创建mock
    @Mock
    // 修饰被测试的类,不能是接口,创建这个类的对象并自动标记为 @Mock,@Spy等注解的属性值 注入到这个类中
    @InjectMocks
    // 在执行测试之前执行被注接的方法
    @BeforeEach //junit5
    @Before //junit4
    // 在执行测试之后执行被注解的方法
    @AfterEach //junit5
    @After //junit4
    // Spy对象会走对象真是的方法,而mock对象不会
    // Spy作用于对象实例,而Mock作用于 .class
    @Spy

mock static,finnal方法

mvn依赖: inline 与 core 不能同时存在,会有问题

        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-inline</artifactId>
            <version>4.6.1</version>
            <scope>test</scope>
        </dependency>

static 方法

public class StaticMethod {
    public static List<Integer> range(int s, int e) {
        List<Integer> list = new ArrayList<>();
        for (int i = s; i <= e; i++) {
            list.add(i);
        }
        return list;
    }
    public static String name() {
        return "name";
    }
}
public class StaticMethodTest {
    @Test
    public void range() {
        // Mockito.mockStatic 使用完了需要关闭,否则启动多个Test会产生问题。原因是Mockito使用了ThreadLocal中,用完必须得释放
        try (MockedStatic<StaticMethod> mockStatic = Mockito.mockStatic(StaticMethod.class)) {
            // stubbing
            mockStatic.when(() -> StaticMethod.range(2, 5)).thenReturn(new ArrayList<Integer>(Arrays.asList(2, 3, 4, 5)));
            // using
            Assert.assertTrue(StaticMethod.range(2, 5).contains(3));
        }
    }
    @Test
    public void name() {
        try (final MockedStatic<StaticMethod> mockStatic = Mockito.mockStatic(StaticMethod.class)) {
            mockStatic.when(StaticMethod::name).thenReturn("hello you");

            System.out.println(StaticMethod.name());
        }
    }
}

final 方法

mock 问题

mock 知识拷贝一个,拷贝和创建有什么区别?

代码覆盖率的问题

注意:打桩的api及代码参数 要和 执行的代码参数完全一直,否则就会报错,mock执行后的返回值就会是默认值。

public class DaoException extends RuntimeException {
}
public class FindUtils {
    public static String getAreaCode(String number) {
        return "a";
    }

    public static String getOperatorCode(String number) {
        return "b";
    }
}
public interface RegistrationService {
    String register(String name, String phone) throws ValidationException;
}
public class RegistrationServiceImpl implements RegistrationService {
    private static final String pattern = "";
    @Resource
    SalesDao salesDao;
    @Resource
    UserDao userDao;

    @Override
    public String register(String name, String phone) throws ValidationException {
        // 参数校验
        if (name == null || name.length() == 0) {
            throw new ValidationException("length 不能为空");
        }
        if (phone == null || !isValid(phone)) {
            throw new ValidationException("phone 不能为空");
        }
        // 获取手机号归属地编号 和 运营商编号 然后通过编号找到区域内是 SalesRep
        String areaCode = FindUtils.getAreaCode(phone);
        String operatorCode = FindUtils.getOperatorCode(phone);

        String user = "";
        try {
            int repId = salesDao.findRep(areaCode, operatorCode);
            user = userDao.save(name, phone, repId);
        } catch (SQLException e) {
            throw new DaoException();
        }
        return user;
    }

    public boolean isValid(String phone) {
        return phone.equals("12345678911");
//        return phone.matches(pattern);
    }
}
public class SalesDao {
    public int findRep(String areaCode, String operationCode) throws SQLException {
        return 1;
    }
}
public class UserDao {
    public String save(String v1, String v2, int v3) throws SQLException {
        return "real 1";
    }
}

mock的大致原理——父子类/派生类(cglib)

public class User {
}
public class SimpleDao {
    public void insertUser(User user) {
        throw new RuntimeException("the database is not connect");
    }
}
public class SimpleService {
    SimpleDao simpleDao;

    public SimpleService(SimpleDao simpleDao) {
        this.simpleDao = simpleDao;
    }

    public void save(User user) {
        // ...

        // save to db:数据不可用,是原创的,则需要mock技术来测试
        simpleDao.insertUser(user);

        // ...
    }
}
public class SimpleServiceTest {
    SimpleDaoMock mock;

    public SimpleServiceTest(SimpleDaoMock simpleDao) {
        mock = simpleDao;
    }

    public static void main(String[] args) {
        // 创建一个mock
        SimpleDaoMock mock = new SimpleDaoMock();
        // 测试 SimpleService 的 save方法
        User user = new User();
        SimpleService service = new SimpleService(mock);
        // 用mock来替换api里使用的成员变量。调用api。 如果要mock的对象是局部变量,则mockito无法做,只有通过powermock来修改字节码的方式做
        service.save(user);
    }

    // 父子类技术,派生一个Mock对象
    static class SimpleDaoMock extends SimpleDao {
        @Override
        public void insertUser(User user) {
            System.out.println("mock insert user");
        }
    }
}

mock存在的问题

如果要测试的API是这样的,需要mock的对象是局部变量,则mock无能为力

public class SimpleService {
    public void save(User user) {
        // ...

        // save to db:数据不可用,是原创的
        simpleDao.insertUser(user);

        // ...
    }
}

解决方法:使用powermock,修改字节码

powermock 实战

  • 排除bug
  • 程序依赖某些其他库/组件,但是本机没有
    powermock 依赖于 easymock mockito

pom依赖

        <!-- https://mvnrepository.com/artifact/org.powermock/powermock-module-junit4 -->
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-module-junit4</artifactId>
            <version>2.0.9</version>
            <scope>test</scope>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.powermock/powermock-api-mockito2 -->
        <dependency>
            <groupId>org.powermock</groupId>
            <artifactId>powermock-api-mockito2</artifactId>
            <version>2.0.9</version>
            <scope>test</scope>
        </dependency>

powermock 基本用法

约定:service 调用 dao 并且 dao 不可用

mock 局部变量

public class UserDao {
    public int getCount() {
        return 1;
    }

    public void insert(User user) {

    }
}
public class UserServiceLocalVariable {

    public int queryUserCount() {
        UserDao userDao = new UserDao();
        return userDao.getCount();
    }

    public void insertUser(User user) {
        UserDao userDao = new UserDao();
        userDao.insert(user);
    }
}
@RunWith(PowerMockRunner.class)
@PrepareForTest({UserServiceLocalVariable.class})
public class UserServiceLocalVariableTest {

    @Test
    public void testCount() {
        try {
            UserServiceLocalVariable service = new UserServiceLocalVariable();
            // 先mock一个useDao 以备用,等下要注入到要测试的api的局部变量里面
            UserDao userDaoMock = PowerMockito.mock(UserDao.class);
            // 模拟 UserDao的 (无参)构造函数
            PowerMockito.whenNew(UserDao.class).withNoArguments().thenReturn(userDaoMock);
            // 模拟 userDaoMock 的具体API 的行为
            PowerMockito.doReturn(10).when(userDaoMock).getCount();
            int count = service.queryUserCount();
            Assert.assertEquals(10, count);
        } catch (Exception e) {
            Assert.fail();
        }
    }

    @Test
    public void testSave() {
        try {
            UserServiceLocalVariable service = new UserServiceLocalVariable();
            UserDao userDaoMock = PowerMockito.mock(UserDao.class);
            PowerMockito.whenNew(UserDao.class).withNoArguments().thenReturn(userDaoMock);
            User user = new User();
            PowerMockito.doNothing().when(userDaoMock).insert(user);
            service.insertUser(user);
            // 验证是否调用了 mock 的insert方法
            Mockito.verify(userDaoMock).insert(user);
        } catch (Exception e) {
            Assert.fail();
        }
    }
}

mock static方法

public class UserService {

    public int queryUserCount() {
        return UserDao.getCount();
    }

    public void insertUser(User user) {
        UserDao.insert(user);
    }
}
public class UserDao {
    public static int getCount() {
        return 1;
    }

    public static void insert(User user) {
    }
}
@RunWith(PowerMockRunner.class)
@PrepareForTest({UserDao.class}) //https://blog.csdn.net/qaz296490709/article/details/72880370
public class UserServiceTest extends TestCase {

    public void testQueryUserCount() throws Exception {
        PowerMockito.mockStatic(UserDao.class);
        UserService userService = new UserService();
        PowerMockito.when(UserDao.getCount()).thenReturn(10);
        int count = userService.queryUserCount();
        Assert.assertEquals(count, 10);
    }

    public void testInsertUser() {
        PowerMockito.mockStatic(UserDao.class);
        User user = new User();
        PowerMockito.doNothing().when(UserDao.class);
        UserService userService = new UserService();
        userService.insertUser(user);
        PowerMockito.mockStatic(UserDao.class);
    }
}

mock final 类

无法继承,无法重写方法。

// 不管是 final 类 还是 finnal方法都可以
public final class UserDao {

    public int getCount() {
        return 1;
    }
    public void insert(User user) {
    }

}
public class UserService {

    private UserDao userDao;

    public UserService(UserDao userDao) {
        this.userDao = userDao;
    }

    public int queryUserCount() {
        return userDao.getCount();
    }

    public void insertUser(User user) {
        userDao.insert(user);
    }
}
// 使用注解和使用new的方式都可以
@RunWith(PowerMockRunner.class)
@PrepareForTest({UserDao.class})
public class UserServiceTest {

    @Mock
    UserDao userDao;
    @InjectMocks
    UserService userService;

    @Test
    public void count() {
//        UserDao userDao = PowerMockito.mock(UserDao.class);
        System.out.println(userDao.getClass());
        PowerMockito.doReturn(10).when(userDao).getCount();
//        UserService userService = new UserService(userDao);
        int count = userService.queryUserCount();
        Assert.assertEquals(count, 10);
    }

    @Test
    public void insert() {
//        UserDao userDao = PowerMockito.mock(UserDao.class);
        System.out.println(userDao.getClass());
        User user = new User();
        PowerMockito.doNothing().when(userDao).insert(user);
//        UserService userService = new UserService(userDao);
        userService.insertUser(user);
        Mockito.verify(userDao).insert(user);
    }
}

verify 方法

public class UserDao {
    public int getCount() {
        throw new UnsupportedOperationException();
    }

    public void insert(User user) {
        throw new UnsupportedOperationException();
    }

    public void updateUser(User user) {
        throw new UnsupportedOperationException();
    }
}
public class UserServiceLocalVariable {

    public void saveOrUpdate(User user) {
        UserDao userDao = new UserDao();
        if (userDao.getCount() > 0) {
            userDao.updateUser(user);
        } else {
            userDao.insert(user);
        }
    }
}

@RunWith(PowerMockRunner.class)
@PrepareForTest({UserServiceLocalVariable.class})
public class UserServiceLocalVariableTest {

    @Mock
    UserDao userDao;

    @InjectMocks
    UserServiceLocalVariable userServiceLocalVariable;

    @Test
    public void test() throws Exception {
        //
        PowerMockito.whenNew(UserDao.class).withNoArguments().thenReturn(userDao);
        User user = new User();
        PowerMockito.doReturn(1).when(userDao).getCount();
        PowerMockito.doNothing().when(userDao).updateUser(user);
        PowerMockito.doNothing().when(userDao).insert(user);

        userServiceLocalVariable.saveOrUpdate(user);

        Mockito.verify(userDao).getCount();
        Mockito.verify(userDao, Mockito.never()).insert(user);
        Mockito.verify(userDao).updateUser(user);
    }
}

mock 构造函数

public class UserService {
    public void save(String username, String password) {
        UserDao userDao = new UserDao(username, password);
        System.out.println(userDao.getClass());
        userDao.insert();
    }
}
public class UserDao {

    private String username;
    private String password;
    public UserDao() {}
    public UserDao(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public void insert() {
        throw new UnsupportedOperationException();
    }
}
@RunWith(PowerMockRunner.class)
@PrepareForTest({UserService.class})
public class UserServiceTest extends TestCase {

    @InjectMocks
    UserService userService;

    @Mock
    UserDao userDao;

    @Test
    public void testInsertUser() throws Exception {
        String username = "11", password = "22";
        PowerMockito.whenNew(UserDao.class).withArguments(username, password).thenReturn(userDao);
        System.out.println(userDao.getClass());
        // 没有打桩成功
        PowerMockito.doNothing().when(userDao).insert();
        userService.save(username, password);
        Mockito.verify(userDao).insert();
    }
}

ArgumentsMatchers 接口

对于多个参数都返回一个值的情况们可以用 MatcherArgumentsMatchers 接口

public class UserDao {

    public String queryByName(String name) {
        return "ss";
    }
}
public class UserService {
    public String find(String name) {
        return new UserDao().queryByName(name);
    }
}
@RunWith(PowerMockRunner.class)
@PrepareForTest({UserService.class})
public class UserServiceTest extends TestCase {

    @Mock
    UserDao userDao;
    @InjectMocks
    UserService userService;

    @Test
    public void testFind() throws Exception {
        PowerMockito.whenNew(UserDao.class).withNoArguments().thenReturn(userDao);
        PowerMockito.when(userDao.queryByName(Matchers.argThat(new MyArgMatchers()))).thenReturn("wdh");
        Assert.assertEquals("wdh", userService.find("11"));
        Assert.assertEquals("wdh", userService.find("22"));
        Assert.assertEquals("wdh", userService.find("33"));
    }

    static class MyArgMatchers implements ArgumentMatcher<String> {
        @Override
        public boolean matches(String s) {
            switch (s) {
                case "11":
                case "22":
                case "33":
                    return true;
                default:
                    return false;
            }
        }
    }
}

Answer 接口

Matcher的增强版,不同的参数对应不同的结果。

public class UserDao {
    public String queryByName(String name) {
        return "ss";
    }
}
public class UserService {
    public String find(String name) {
        return new UserDao().queryByName(name);
    }
}
@RunWith(PowerMockRunner.class)
@PrepareForTest({UserService.class})
public class UserServiceTest {
    @Mock
    UserDao userDao;
    @InjectMocks
    UserService userService;

    @Test
    public void testFind() throws Exception {
        PowerMockito.whenNew(UserDao.class).withNoArguments().thenReturn(userDao);

        PowerMockito.when(userDao.queryByName(Mockito.anyString())).then((invocation) -> {
            String arg = (String) invocation.getArguments()[0];
            switch (arg) {
                case "11":
                    return "I am 11";
                case "22":
                    return "I am 22";
                case "33":
                    return "I am 33";
                default:
                    throw new RuntimeException("Not support: " + arg);
            }
        });
        Assert.assertEquals("I am 11", userService.find("11"));
        Assert.assertEquals("I am 22", userService.find("22"));
        Assert.assertEquals("I am 33", userService.find("33"));
    }
}

Spy 以及 private 方法

Spy 创建的对象是执行真实的方法。
如果打桩的行为和真实测试的行为是一致的,则不会执行真实的方法。否则执行的是真实的方法

public class UserService {
    public void foo(String msg) {
        log(msg);
    }

    public void log(String msg) {
        System.out.println("I am a service:" + msg);
    }

    public void exit(String msg) {
        checkExit(msg);
    }

    private void checkExit(String msg) {
        System.out.println("msg:"+ msg);
        throw new UnsupportedOperationException();
    }
}
@RunWith(PowerMockRunner.class)
@PrepareForTest({UserService.class})
public class UserServiceTest {

    @Test
    public void testFoo() {
        UserService spy = PowerMockito.spy(new UserService());
        PowerMockito.doNothing().when(spy).foo("bb");
        spy.foo("bb"); // 没有任何输出
//        spy.foo("ss"); // 有输出
    }

    @Test
    public void testPrivate() throws Exception {
        UserService spy = PowerMockito.spy(new UserService());
        PowerMockito.when(spy, "checkExit", "aa");
        spy.exit("aa");
//        spy.exit("bb");
    }
}

总结

  • powermock 不是重复造轮子
  • powermock是完成其他mock不能完成的任务
  • 尽量减少使用powermock

注解 @PrepareForTest

  • 当使用PowerMockito.whenNew方法时,必须加注解@PrepareForTest@RunWith。注解@PrepareForTest里写的类是需要mock的new对象代码所在的类。

  • 当需要mock final方法的时候,必须加注解@PrepareForTest@RunWith。注解@PrepareForTest里写的类是final方法所在的类。

  • 当需要mock static 方法的时候,必须加注解@PrepareForTest@RunWith。注解@PrepareForTest里写的类是静态方法所在的类。

  • 当需要mock private方法的时候, 只是需要加注解@PrepareForTest,注解里写的类是私有方法所在的类

  • 当需要mock系统类的静态方法的时候,必须加注解@PrepareForTest@RunWith。注解里写的类是需要调用系统方法所在的类

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值