【问题标题】:How can I make sure no method of an instance is being called?如何确保没有调用实例的方法?
【发布时间】:2013-02-28 14:17:03
【问题描述】:

我的代码中有几个small dieties 在测试时会引起痛苦。假设将小神打成碎片对于这个练习来说太费力了。

通常的问题是我想测试 Foo 的方法 x() 但要创建 Foo 的实例,我需要定义 N (1 @Autowire。在生产中,我必须确保这些字段是连线的,因此不能选择这些字段。

我看到的可能解决方案:

  1. 以某种方式告诉 Spring 一些 @Autowire 字段在测试期间是可选的
  2. 为被测代码不需要/不应该需要的任何内容传递不可调用的模拟。

由于我不知道#1 的方法,我认为#2 是要走的路。我更喜欢的是一个 bean 工厂,它返回一个代理,该代理会为我未定义的任何 bean 的任何方法调用引发异常。

所以对于任何未知的bean,它应该调用这个create方法:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class MustNotCallMe {

    @SuppressWarnings( "unchecked" )
    public static <T> T create( final Class<T> type, final Class<?>... types ) {
        InvocationHandler handler = new InvocationHandler() {

            @Override
            public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable {

                if( "equals".equals( method.getName() ) && Object.class.equals( method.getDeclaringClass() ) ) {
                    return proxy == args[0];
                }

                throw new UnsupportedOperationException( "You must not call " + method );
            }
        };

        Class<?>[] allClasses = new Class<?>[ types.length + 1 ];
        allClasses[0] = type;
        System.arraycopy( types, 0, allClasses, 1, types.length );

        return (T) Proxy.newProxyInstance( MustNotCallMe.class.getClassLoader(), allClasses, handler );
    }
}

这样的东西存在吗?如果没有,我将如何在 Spring 3 单元测试中注入我自己的 bean 工厂?

编辑我知道这个想法会让任何语言纯粹主义者感到不安。只有现实很少是纯粹的。如果这里有人愿意站出来为我们提供重构软件所需的资金和人力,我们很乐意听到。在那之前,无需大量手动工作即可解决问题的解决方案可以更好地解决我们的特定问题:-)

也就是说,我只需要一种方法来创建一个 BeanFactory,它永远不会抛出 NoSuchBeanException,而是返回一个“不要打电话给我”的代理。

【问题讨论】:

  • 为什么特别需要豆厂?我通常希望您使用任何自动装配来测试代码而不使用 - 只需将值传递给普通的构造函数(或设置属性)。因此,正常的严格模拟(例如通过 Mockito 或 EasyMock)应该没问题。
  • 你的 spring 版本是 3.0.X 吗?从 3.1.X 版本开始,spring 支持profiles,它们可能对您有所帮助。
  • 我正在使用 Spring 3.2 和 Mockito,如果有帮助的话。
  • @JonSkeet:我也喜欢这样,但是代码变得太复杂了,我们现在没有人手来修复它,所以我会接受第二好的解决方案。

标签: java unit-testing mocking


【解决方案1】:

我对这个领域还很陌生,但我认为 Spring 的整个想法是很容易更换部件以进行测试和其他环境更改。

从一个(认为他)理解概念但没有实现任何主要内容的人的角度来看,这有什么问题:每个自动装配的部分都是一个接口。想象一个实现该接口的类,其中每个方法仅调用某种错误报告机制——它可以对所有方法都是同一个。创建这样一个类(手动)应该很快,不容易出错,现在你有了你的“模拟”。如果您有 10 个这样的类,那么为每个类创建这样的类应该不会花那么长时间。

现在创建一个 spring 配置文件,该文件传递给定测试集所需的这些模拟。如果有东西调用了一个模拟类,它会吐出上面的错误。当然,针对不同的测试环境,不同的配置可以包含不同的模拟。

我在这里缺少什么?

【讨论】:

  • 我可以手动完成所有这些工作,或者让程序为我完成。我的观点是编写这段代码很容易;我只是不知道如何将它融入 Spring 的 BeanFactory API。
【解决方案2】:

黄金路径是:不要在测试中使用 Spring,只需使用普通的 java 代码设置依赖项。如果这很痛苦,那么您就有了如何重构代码的有力指标。这是一件好事。对于不应调用的依赖项,您提供在每次方法调用时都会爆炸的模拟。实现这一点的一个简单方法应该是这个漂亮的 Mockito 功能:http://docs.mockito.googlecode.com/hg/org/mockito/Mockito.html#14

肮脏的道路:如果您陷入了无法忍受在测试中设置所有依赖项的遗留沼泽中(并且小神的存在是一个指标),您可以使用SpringJUnit4ClassRunner + Spring 配置文件(我认为如果您使用 Spring 3.1 或更高版本)。

有了这个,您可以拥有一个用于生产环境的prod 配置文件和一个integrationtest 配置文件,它用上述模拟替换所有不能调用的bean。谷歌“spring + profiles”查看各种教程。它很简单,而且工作起来就像一个魅力......但只有当你有一套固定的豆子想要更换时才适合。如果您有 50 个测试,每个测试都需要 20 个不同的 bean 子集,而这些 bean 被 Blowing-up-on-call-beans 替换,情况会变得更糟:

地狱般的道路:使用 Spring,但在每个测试级别上将一组特定的 bean 替换为 Blowing-up-on-call-beans。您可以通过特殊的上下文配置来做到这一点,其中包含要替换的单个 bean 的配置。它必须与生产 bean 具有相同的名称/ID,但在调用实现时会爆炸(如果您愿意,可以再次通过 Mockito)。然后在每个测试中使用 ContextConfiguration 注解,如下所示:

@ContextConfiguration(locations = {"applicationContext.xml", "blowUpDemiGod42.xml",
    "blowUpDemiGod23.xml"})

这将使blowUpDemiGod*.xml 文件中的配置覆盖applicationContext.xml 中的配置

可能有一种地狱般的路径的简单形式,您可以在其中通过 Java 代码在测试中提供 bean ...不确定。

【讨论】:

  • 我喜欢 Mockito 功能,因为我已经将它用于其他测试...... Mockito 的功能太多了 ;-) 但是真的没有办法创建“自动构造器 BeanFactory”吗?
猜你喜欢
  • 2022-06-14
  • 1970-01-01
  • 1970-01-01
  • 2021-10-02
  • 1970-01-01
  • 1970-01-01
  • 2016-08-30
  • 1970-01-01
  • 2023-03-30
相关资源
最近更新 更多