3

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

通常的问题是我想测试一个方法x()Foo但是要创建一个实例Foo,我需要定义 N (1 < N < 10) 个模拟实例,这些实例只是为了满足 Spring 的@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但返回一个“不要打电话给我”的代理。

4

2 回答 2

1

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

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

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

我在这里想念什么?

于 2013-02-28T14:48:52.110 回答
1

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

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

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

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

于 2013-02-28T15:12:33.260 回答