Java单元测试 – 模拟所有类方法返回一个结果

huangapple go评论71阅读模式
英文:

Java Unit Testing - Mock all class methods to return one result

问题

以下是翻译好的内容:

我想要对一个类上的所有方法进行模拟,使其返回一个结果,而不是分别对每个方法进行模拟,类似于:

mock(Foo.class, allMethodsWithAnyArgs).thenReturn('abc');

我对Java测试非常新手,请原谅这个愚蠢的问题。这个大型项目中包含了Mockito、PowerMockito、PowerMock、EasyMock,可能还有其他的东西,所以我可以使用任何推荐的工具。

一个扩展示例可能如下,我需要测试Foo:

public class Foo {
    doSomething(){
        Bar bar = new Bar();
        
        // 一些代码
    
        bar.x(a);

        // 一些代码

        bar.x(a, b, c);

        // 更多代码

        bar.y(...);

        // 更多代码

        bar.z(...);
    }
}

我需要对所有bar调用进行存根处理,但不关心返回值,或者希望所有调用的返回值相同,比如都是1或者'ok'。

更新:
我同意这不是最佳的测试代码,下面是一个我尝试在这种情况下进行测试的更新示例,所以我需要对MyUtil和bar方法进行存根处理,它们是静态的,那么你建议我如何重构它们?将MyUtil作为必需的参数放进去对我来说是没有意义的,如果可能的话,我宁愿保持Foo为静态的。如果能够提供一个可工作的测试文件,我将不胜感激,因为我已经在解决这类问题上努力了几天。

public class Foo {
    public static Object doSomething(Long id){
        Bar bar = new Bar();
        Object obj;

        if(somecondition) {
            Long vnumber = MyUtil.getVNumber(id);

            obj = bar.getCurrentObj(id, vnumber);            
        } else {
            obj = bar.getPreviousObj(id);
        }

        bar.z(obj);
        ....
    }
}

此类的一个要点之一是使其成为一个黑盒,我不希望调用者担心创建实例并将它们传递进去,甚至不需要知道事情的工作原理,我只想传递一个ID并获取其结果。一些调用函数甚至根本没有访问Bar的权限,所以我无法将其作为必需参数。

英文:

I would like to mock ALL methods on a class to return one result, instead of mocking each separately, something like:

mock(Foo.class, allMethodsWithAnyArgs).thenReturn('abc');

I'm very new to Java testing so please forgive the dumb question. This giant project has Mockito, PowerMockito, PowerMock, EasyMock, and probably other stuff so I can use anything recommended.

An extended example would be something like this, where I need to test Foo:

public class Foo {
    doSomething(){
        Bar bar = new bar();
        
        // some code

        bar.x(a);

        // some code

        bar.x(a, b, c);

        // some more code

        bar.y(...);

        // even more code

        bar.z(...);
    }
}

I need to stub all the bar calls but don't care about the return, or I want the same return, say 1 or 'ok' from all of them, etc.

UPDATE:
I agree this isn't the best code to test, here is an updated example of what I am trying to test in this case, so I need to stub MyUtil and the bar methods, these are static, so how do you suggest I refactor them? It doesn't make sense to me to put MyUtil as a required argument, and I would prefer to keep Foo as static if possible. A working test file would be super appreciated as I have been struggling with this kind of thing for a few days now.

public class Foo {
    public static Object doSomething(Long id){
        Bar bar = new Bar();
        Object obj;

        if(somecondition) {
            Long vnumber = MyUtil.getVNumber(id);
            
            obj = bar.getCurrentObj(id, vnumber);            
        } else {
            obj = bar.getPreviousObj(id);
        }
        
        bar.z(obj);
        ....
    }
}

Also, one of the points of this class was to make it a black box, I don't want the called to worry about creating instances and passing them in, or even knowing how things work, I just want to pass an ID and get a result for it. Some of the calling functions don't even HAVE access to Bar for example so I can't make it a required parameter.

答案1

得分: 1

拥有如此多的模拟方法可能是一个具有过多职责的类的迹象。

然而,模拟并不是唯一的测试替身。更适合您的用例的可能是一个虚拟对象

这将取决于 Foo 是否在接口的后面:

interface Bar {

   String x(String s);

   String y(String s);
}

然后您有您的生产实现:

class RealBar implements Bar {
   
    @Override
    String x(String s) {
        // 无论您的生产逻辑是什么
    }
}

然后是一个您在测试中使用的虚拟对象,它位于您的 test 源集中:

class FakeBar implements Bar {

    @Override
    String x(String s) {
        return "abc";
    }
}

因为这个虚拟对象可以直接使用,您可以重复使用它,而且不需要为它手动设置行为。

如果您在 Foo 中使用构造函数注入,您的测试将包括将您的虚拟 Bar 传递给它,而不是生产 Bar

@Before
public void setUp() {
   foo = new Foo(new FakeBar());
}

因此,虚拟对象是避免手动设置存根负担的被接受的方式。

然而,如果您没有其他方法来为类的每个单独方法设置存根以返回相同的值,这不是一个常见的用例,也不太可能得到大多数模拟库的支持。特别是当 Mockito 试图对类设计持有意见时,以防止您编写次优代码。

您可以手动编写代码,使用反射查找已声明的方法,然后设置存根行为。当您做到这一步时,最好执行"提取接口"并编写一个虚拟对象。

英文:

Having so many methods to mock can be a sign of a class with too many responsibilities.

However, mocks are not the only test doubles. What might better suit your use case is a fake.

This would really depend on Foo being behind an interface:

interface Bar {

   String x(String s);

   String y(String s);
}

then you have your prod implementation:

class RealBar implements Bar {
   
    @Override
    String x(String s) {
        // whatever your production logic is
    }
}

and then a fake you use in the test that sits in your test source set:

class FakeBar implements Bar {

    @Override
    String x(String s) {
        return "abc";
    }
}

Because the fake works out-of-the-box, you can reuse it and you don't have to manually stub behaviors for it.

If you use constructor injection in Foo, your test will consist of passing in your fake Bar instead of the production Bar

@Before
public void setUp() {
   foo = new Foo(new FakeBar());
}

Fakes are thus the accepted way of avoiding the burden of manually stubbing.

However, if you have no alternative to stubbing every single method of a class to return the same value, this is not a common use case and unlikely to be supported by most mocking libraries. Especially when Mockito tries to be opinionated on class design in order to prevent you from writing suboptimal code.

You could manually write code that used reflection to find the declared methods and then stub the behavior. By the time you do this, may as well have performed "Extract interface" and written a fake.

huangapple
  • 本文由 发表于 2020年4月4日 01:42:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/61017531.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定