mockito和powermock的doAnswer使用以及Fake思想

序言

对于mock我们已经在前面一篇文章里面详细介绍了。那篇文章里面介绍的方式可以解决我们大部分的问题,但是,是的,又是但是。有些中间过程,中间变量的verify,需要我们考虑更多。对于简单类型,mockito和powermock已经提供了verify方法,这里的重点在于不是简单类型,我们关注的是对象。对于中间变量是对象的verify,简单的verify函数力有未逮。对于这种情况,我们可以使用doAnswer或者Fake的思想。

Fake的思想

对于fake这种思想,我用下面的例子来表达。

A a;
........
a.method(Obj)

使用Fake的思想,我们会把A抽象成interface,然后真实的场景中,A的一个实例调用method去做事情,而我们需要对于Obj这个中间变量进行验证,这个时候我们需要拿到Obj的值。通常的做法就是我们自己实现一个A的实例,然后实现method方法,这个方法里面只是简单的把变量Obj的值存储下来,在后面的验证中取出来使用。

这里把A抽象成interface是一种隔离的思想,因为a.method也许是DB操作,或者是连接打印机等等行为,我们希望a.method不影响我们对于上面逻辑的验证,所以我们把它抽成interface,抽象出来。在我们fake之后,如果我们的测试没有问题,但是整个功能比如DB操作,或者是打印问题,我们就可以确认是a.method的问题了。

下面我们来看看具体的实现。

-------------- PersonPrinter.java ---------------
package com.mock;

public interface PersonPrinter {
    void printPerson(HighSchoolStudent highSchoolStudent);
}
-------------- Person.java ---------------
package com.mock;

public interface Person {
    String getName();
}
-------------- HighSchoolStudent.java ---------------
package com.mock;

public class HighSchoolStudent implements Person{
    private String name;
    private int age;
    private int id;
    private int grade;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public int getAge() {
        return age;
    }

    @Override
    public String getName() {
        return name;
    }

    public void setGrade(int grade) {
        this.grade = grade;
    }

    public int getGrade() {
        return grade;
    }
}
---------------- Student.java---------------
package com.mock;

public class Student {
    private PersonPrinter personPrinter;

    public void doAnswerHighSchoolStudent(){
        HighSchoolStudent highSchoolStudent = new HighSchoolStudent();
        highSchoolStudent.setName("highSchoolStudent");
        highSchoolStudent.setAge(10);
        highSchoolStudent.setId(123456789);
        //verify highSchoolStudent
        personPrinter.printPerson(highSchoolStudent);
        highSchoolStudent.setGrade(3);
    }

    public void setPersonPrinter(PersonPrinter personPrinter) {
        this.personPrinter = personPrinter;
    }
}

我们希望verify中间结果highSchoolStudent是否正确,所以我们要拿到highSchoolStudent这个中间变量。

现在personPrinter.printPerson(highSchoolStudent); personPrinter是interface,所以根据我们fake的思想,我们不要抽象成interface了,只需要实现PersonPrinter这个interface就好。

------------------ FakePersonPrinter.java --------------
package com.mock;

public class FakePersonPrinter implements PersonPrinter {
    private HighSchoolStudent highSchoolStudent;

    @Override
    public void printPerson(HighSchoolStudent highSchoolStudent) {
        this.highSchoolStudent = highSchoolStudent;
    }


    public HighSchoolStudent getHighSchoolStudent() {
        return highSchoolStudent;
    }
}

这个fake类里面,我们只是把中间变量highSchoolStudent存储下来,当我们测试的时候,我们就可以很容易拿到它了。

 @Test
 public void testFakeObject() {
     Student student = new Student();
     PersonPrinter personPrinter = new FakePersonPrinter();
     //注入fake object
     student.setPersonPrinter(personPrinter);
    //调用要测试的方法
     student.doAnswerHighSchoolStudent();
    //从fake对象里面取出中间变量
     HighSchoolStudent highSchoolStudent = ((FakePersonPrinter) personPrinter).getHighSchoolStudent();
     assertThat(highSchoolStudent.getId(), is(123456789));
     assertThat(highSchoolStudent.getAge(), is(10));
     assertThat(highSchoolStudent.getName(), is("highSchoolStudent"));
     assertThat(highSchoolStudent.getGrade(), is(3));
 }

通过上面的方式,我们就可以测试一些中间过程的逻辑了。上面的测试没有使用mock的库,mockito和powermock已经提供了上述过程的verify。那就是我们将要提到的doAnswer。

扫描二维码关注公众号,回复: 1443093 查看本文章

DoAnswer的用法

Student和PersonPrinter仍然保持不变,下面我们用doAnswer的方式,实现fake类似的作用。
首先我们需要实现powermock的Answer这个interface。

---------------- PersonPrinterAnswer.java ------------------
package com.mock;

import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

public class PersonPrinterAnswer implements Answer<Object> {

    private Person person;

    @Override
    public Person answer(InvocationOnMock invocationOnMock) {
        Object[] args = invocationOnMock.getArguments();
        person = (Person) args[0];
        return person;
    }

    public Person getPerson() {
        return person;
    }
}

我们需要mock PersonPrinter这个interface,当
personPrinter.printPerson(highSchoolStudent);
的时候我们就返回这个PersonPrinterAnswer。而answer()函数的参数就是personPrinter.printPerson()函数传进来的参数。通过这种方式我们就可以拿到中间变量highSchoolStudent。这里我同样会把中间变量记录下来,以供后面的过程使用。

在test里面我们只需要使用doAnswer就可以了。

 @Test
 public void testInterfaceDoAnswer() {
     //mock一下PersonPrinter
     PersonPrinter personPrinter = spy(PersonPrinter.class);
     PersonPrinterAnswer personPrinterAnswer = new PersonPrinterAnswer();
    //使用我们定义的personPrinterAnswer
     doAnswer(personPrinterAnswer).when(personPrinter).printPerson(anyObject());
     Student student = new Student();
     //把我们mock的personPrinter注入student
     student.setPersonPrinter(personPrinter);
     //要测试的方法
     student.doAnswerHighSchoolStudent();
     //取出我们存在answer中的中间变量
     HighSchoolStudent highSchoolStudent = (HighSchoolStudent) personPrinterAnswer.getPerson();
     assertThat(highSchoolStudent.getId(), is(123456789));
     assertThat(highSchoolStudent.getAge(), is(10));
     assertThat(highSchoolStudent.getName(), is("highSchoolStudent"));
     assertThat(highSchoolStudent.getGrade(), is(3));
 }

通过使用Answer,我们verify了真正的临时对象的逻辑。对于不是interface的调用,fake就不是很方便了,但是Answer是可以达到目的的。这里我想要多举一个Answer用于static method的例子。

--------------- CommonPrinter.java --------------
package com.mock;

public class CommonPrinter {
    public static void printPerson(Person person) {

    }
}
--------------- Student.java --------------
package com.mock;

public class Student{

    private PersonPrinter personPrinter;

    public void doAnswerHighSchoolStudent(){
        HighSchoolStudent highSchoolStudent = new HighSchoolStudent();
        highSchoolStudent.setName("highSchoolStudent");
        highSchoolStudent.setAge(10);
        highSchoolStudent.setId(123456789);
        personPrinter.printPerson(highSchoolStudent);
        highSchoolStudent.setGrade(3);
    }

    public void callStaticDoAnswerHighSchoolStudent(){
        HighSchoolStudent highSchoolStudent = new HighSchoolStudent();
        highSchoolStudent.setName("highSchoolStudent");
        highSchoolStudent.setAge(10);
        highSchoolStudent.setId(123456789);
        //调用static方法
        CommonPrinter.printPerson(highSchoolStudent);
        highSchoolStudent.setGrade(3);
    }

    public void setPersonPrinter(PersonPrinter personPrinter) {
        this.personPrinter = personPrinter;
    }
}

我在student里面增加了一个调用static方法的方法
callStaticDoAnswerHighSchoolStudent。在这个方法里面,我调用了static方法
CommonPrinter.printPerson(highSchoolStudent); 下面我们来看看怎么使用Answer保存这种方法的参数。

 @Test
 public void testStaticMethodDoAnswer() throws Exception {
     //mock CommonPrinter
     mockStatic(CommonPrinter.class);
     //Answer没有变化
     PersonPrinterAnswer personPrinterAnswer = new PersonPrinterAnswer();
     //static方法的doAnswer的使用方式
     PowerMockito.doAnswer(personPrinterAnswer).when(CommonPrinter.class, "printPerson", anyObject());
     Student student = new Student();
     //调用测试方法
     student.callStaticDoAnswerHighSchoolStudent();
     //取出存储的中间变量
     HighSchoolStudent highSchoolStudent = (HighSchoolStudent) personPrinterAnswer.getPerson();
     assertThat(highSchoolStudent.getId(), is(123456789));
     assertThat(highSchoolStudent.getAge(), is(10));
     assertThat(highSchoolStudent.getName(), is("highSchoolStudent"));
     assertThat(highSchoolStudent.getGrade(), is(3));
 }

我们的PersonPrinterAnswer并没有改变,这是因为我们测试的方法printPerson的参数没有什么变化,所以我们不需要改变它。

总结

本篇文章主要介绍了怎么测试中间变量。我们可以使用Fake的方式,但是Fake的方式局限性比较大。这个时候,我们就可以选择使用doAnswer的方式。我们这些方法都是为了打通加测试的路,哪种比较简单,以理解,你就可以选择哪一种。

参考资料

[1] 修改代码的艺术/(Michael C. Features)著;刘未鹏译.——北京:人民邮电出版社,2017.11
[2] https://blog.csdn.net/westkingwy/article/details/7485213

猜你喜欢

转载自blog.csdn.net/qisibajie/article/details/80210640