简体   繁体   English

Java:如何对封装的注入或生成的依赖项进行单元测试?

[英]Java: How do I unit test where there are encapsulated injected or generated dependencies?

SUMMARY: I have a self-contained class for performing a proxied login and then filling a HttpServletResponse object with authentication content a browser can use. 简介:我有一个自包含的类,用于执行代理登录,然后用浏览器可以使用的身份验证内容填充HttpServletResponse对象。 When testing my code, how can I provide mocked services into a class that has no setters? 在测试代​​码时,如何向没有设置器的类中提供模拟服务?

DETAILS: I've severely edited my proxied login code into this snippet. 详细信息:我已将我的代理登录代码严格编辑到此代码段中。

  1. It asks the server for a login form. 它要求服务器提供登录表单。
  2. It sends back the credentials. 它发回凭据。
  3. It gets the server's approval and passes it to the browser (response). 它获得服务器的批准,并将其传递给浏览器(响应)。

The trimmed code looks like this: 修剪后的代码如下所示:

    private static final Log log = LogFactory.getLog(MyClass.class);

    @Inject()
    private UserService userService;

    public void performProxyLogin(HttpServletResponse response, 
            UserDTO userDTO, String url) {

        String username = getUsername(userDTO);
        String password = getPasswordFromUserService(username);

        // MyRequest only has data, organizing a Http Request.
        MyRequest myRequest = prepareInitialGetRequest(url);  

        // processURLRequest() encapsulates use of HttpURLConnection.
        // MyResponse only has data, organizing a Http Response.
        MyResponse myResponse = processURLRequest(myRequest); 
        myRequest = prepareLoginRequest(myResponse, username, password);
        myResponse = processURLRequest(myRequest);

        // Transfer data into the response, and from there into the browser.
        fillResponseWithProxiedResult(response, myResponse)
    }

To make this work I think I need to inject a mocked Log or LogFactory, a mocked UserService, and a way of getting a mocked HttpURLConnection. 为了使这项工作有效,我想我需要注入一个模拟的Log或LogFactory,一个模拟的UserService以及一种获取模拟的HttpURLConnection的方法。

However, all of the advice I've seen involves code with setters, which the test suite can use to plug in mocked objects. 但是,我所看到的所有建议都涉及带有setter的代码,测试套件可以使用它们插入模拟对象。

How do I provide my class its needed mocked objects? 如何为我的班级提供所需的模拟对象?

Bite the bullet and provide a package-private setter for this field. 咬一下子弹,并为此字段提供一个打包专用的设置器。

If you want to use mocks, there's no value in letting the injection framework set up a mock which you can inject, since you're adding more ceremony and overhead to the set-up of your test. 如果要使用模拟,让注入框架设置可以注入的模拟没有任何价值,因为这会给测试设置增加更多的仪式和开销。

If you want to validate that you have a service injecting correctly, you wouldn't want to use mocks at all (think "integration test" with real or pseudo-real components). 如果要验证是否有正确注入的服务,则根本不希望使用模拟(想将“集成测试”与真实或伪真实组件一起使用)。

Many of the other answers hint at it, but I'm going to more explicitly say that yes, naive implementations of dependency injection can break encapsulation. 许多其他答案也暗示了这一点,但是我要更明确地说,是的,依赖注入的幼稚实现会破坏封装。 The key to avoiding this is that calling code should not directly instantiate the dependencies (if it doesn't care about them). 避免这种情况的关键是,调用代码不应直接实例化依赖项(如果它不关心它们)。 This can be done in a number of ways. 这可以通过多种方式来完成。 The simplest is simply have a default constructor that does the injecting with default values. 最简单的就是拥有一个默认构造函数,该构造函数使用默认值进行注入。 As long as calling code is only using the default constructor you can change the dependencies behind the scenes without affecting calling code. 只要调用代码仅使用默认构造函数,就可以在后台更改依赖项而不会影响调用代码。 This can start to get out of hand if your dependencies themselves have dependencies and so forth. 如果您的依赖项本身具有依赖项等等,这可能会变得一发不可收拾。 At that point the Factory pattern could come into place (or you can use it from the get-go so that calling code is already using the factory). 到那时,工厂模式可能就位了(或者您可以从一开始就使用它,这样调用代码已经在使用工厂了)。 If you introduce the factory and don't want to break existing users of your code, you could always just call into the factory from your default constructor. 如果您引入了工厂并且不想破坏代码的现有用户,则总是可以从默认构造函数中调用工厂。 Beyond that there's using Inversion of Control. 除此之外,还使用了控制反转。 I haven't used IoC enough to speak too much about it, but there's plenty of questions here on it as well as articles online that explain it much better than I could. 我还没有足够使用IoC来谈论它,但是这里有很多问题,还有在线文章解释得比我好得多。 If it should be truly encapsulated to where calling code cannot know about the dependencies then there's the option of either making the injecting (either the constructor with the dependency parameters or the setters) internal if the language supports it, or making them private and have your unit tests use something like Reflection if your language supports it. 如果应该将其真正封装到调用代码无法知道依赖项的地方,那么可以选择将注入(内部依赖项的构造函数或setters)设置为内部(如果语言支持),或者将其私有化并具有如果您的语言支持,单元测试将使用类似于反射的功能。 If you language supports neither then I suppose a possibility might be to have the class that calling code is instantiating a dummy class that just encapsulates the class the does the real work (I believe this is the Facade pattern, but I never remember the names correctly)] 如果您的语言都不支持,那么我想可能是让调用代码的类实例化一个仅封装该类的伪类,这才是真正的工作(我相信这是Facade模式,但我永远不会正确记住这些名称) )]

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM