【问题标题】:Guice injector in JUnit tests [closed]JUnit测试中的Guice注入器[关闭]
【发布时间】:2011-08-03 18:49:34
【问题描述】:

使用 Guice,在每个 JUnit 测试类中获取一个新的注入器是一个好习惯吗,因为每个测试类都应该是独立的?

【问题讨论】:

    标签: java junit guice code-injection


    【解决方案1】:

    推荐我最近写的这个框架Guice-Behave。

    这很简单,使用两个注解就可以在应用程序的同一上下文中运行测试。

    您可以在 Guice 模块中定义您的模拟,这样很容易重用它们。

    【讨论】:

      【解决方案2】:

      您确实应该避免在单元测试中使用 Guice,因为每个测试都应该足够小,以便可以管理手动 DI。通过在单元测试中使用 Guice(或任何 DI),您隐藏了一个警告,即您的类变得太大并承担了太多责任。

      为了测试引导程序代码和集成测试,可以为每个测试创建不同的注入器。

      【讨论】:

      • 我不同意。使用 Guice,您可以使用 @Inject 并注入没有设置器或构造器的字段。它更具可读性。那么在这种情况下手动依赖应该是什么?我更喜欢使用 Injector 而不是手动反射 API,因为它首先出现在我的脑海中。
      • 我从不直接注入没有设置器的字段。我几乎从不使用 setter 注入。我觉得这两者都很丑陋,并且对所述课程的用户隐藏了课程要求。我尝试只使用 ctor 注入。通过在单元测试中使用 Guice(或任何 DI),你隐藏了一个警告,即你的类变得越来越大并承担了太多的责任。
      • 您是否倾向于编写“浅层”单元测试来模拟测试对象的直接依赖关系?我发现如果您使用真实存储等编写“全栈”测试,手动创建大部分依赖关系树可能会很麻烦。不过,不想争论哪种测试方法更好。
      • 没有“更好”,只有“更适合这个用例”。
      • JUnit 框架用于运行集成测试时会怎样?
      【解决方案3】:

      这取决于所使用的 JUnit 版本。我们的团队已成功使用 Junit4,现在正在研究 JUnit5。

      在 Junit5 中,我们使用扩展。

          public class InjectionPoint implements BeforeTestExecutionCallback {
      
              @Override
              public void beforeTestExecution(ExtensionContext context) throws Exception {
      
                  List<Module> modules = Lists.newArrayList(new ConfigurationModule());
      
                  Optional<Object> test = context.getTestInstance();
      
                  if (test.isPresent()) {
                      RequiresInjection requiresInjection = test.get().getClass().getAnnotation(RequiresInjection.class);
      
                      if (requiresInjection != null) {
                          for (Class c : requiresInjection.values()) {
                              modules.add((Module) c.newInstance());
                          }
                      }
      
                      Module aggregate = Modules.combine(modules);
                      Injector injector = Guice.createInjector(aggregate);
      
                      injector.injectMembers(test.get());
                      getStore(context).put(injector.getClass(), injector);
                  }
      
              }
      
              private Store getStore(ExtensionContext context) {
                  return context.getStore(Namespace.create(getClass()));
              }
      
          }
      

      然后每个测试使用RequiresInjection注解,它可以接受一个内部模块数组进行聚合,或者没有使用默认值。

          @RequiresInjection
          public class Junit5InjectWithoutModuleTest {
      
              @Inject
              private TestEnvironment environment;
      
              @Test
              public void shouldAccessFromOuterModule() {
                  assertThat(environment).isNotNull();
              }
      
          }
      

      这里是注释:

          @ExtendWith(InjectionPoint.class)
          @Retention(RetentionPolicy.RUNTIME)
          @Target({ElementType.TYPE, ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
          public @interface RequiresInjection {
      
              Class<? extends Module>[] values() default {};
      
          }
      

      JUnit5 对我来说仍然是新的,所以我可能正在研究模板,但到目前为止,扩展似乎可以解决问题。

      对于 JUnit4,我们使用类似的方法,除了注入发生在我们自定义测试运行器的 createTest 方法中,然后每个测试都实现一个具有“getModule”方法的 RequiresInjection 接口。

      我可能也应该对 TestNG 大喊一声,因为 Guice 支持是内置的。使用就像这样简单:

      @Guice({SomeObjectModule.class})
          public class MyTest {
      
              @Inject
              SomeObject someObject;    
      
          }
      

      【讨论】:

        【解决方案4】:

        如果有人偶然发现这个问题并想了解如何从单元测试中获得 Guice 注释,请从如下所示的基类扩展您的测试并调用 injector.injectMembers(this);

        public class TestBase {
            protected Injector injector = Guice.createInjector(new AbstractModule() {
                @Override
                protected void configure() {
                    bind(HelloService.class);
                }
            });
        
            @Before
            public void setup () {
                injector.injectMembers(this);
            }
        }
        

        那么你的测试就可以像这样得到一个注入的HelloService

        public class HelloServiceTest extends TestBase {
            @Inject
            HelloService service;
        
            @Test
            public void testService() throws Exception {
               //Do testing here
            }
        }
        

        【讨论】:

        • 您应该注意injectMembers 要测试和需要注入的类,而不仅仅是this 这是测试器类。
        • 应该是HelloServiceTest,而不是HelloServletTest和`HelloService服务;`而不是HelloServlet servlet;?我假设是这样并编辑了您的答案。
        • TestBase 应该是abstract ?
        【解决方案5】:

        看看Guice Berry。

        我现在不建议使用它(文档真的很糟糕),但是看看他们的方法可以让你清楚地知道应该如何在 jUnit 中完成 DI。

        【讨论】:

        • 如果你决定使用 GuiceBerry,你可以创建 @Provides 也有 @TestScoped 注释(stackoverflow.com/a/37979254/345648)(或bind(YourClass.class).in(TestScoped.class);)的函数。这告诉 Guice 每个测试只创建一个实例,而不是 @Singleton,它会使组件跨测试重用,或者没有注释,每次注入时都会创建一个新实例(每个测试可能是多个实例)。跨度>
        【解决方案6】:

        我发现AtUnit 是对 Guice 的极好补充(它甚至可以处理模拟框架集成)。

        这使得单元测试类非常清晰和简洁(永远不会在那里看到Injector),并且在适当的情况下,还可以让您将生产绑定作为单元测试的一部分。

        【讨论】:

        • 如果我是对的,AtUnit 源代码库的最后一次提交是在 2008 年。
        【解决方案7】:

        我认为使用DI 会使单元测试代码更简单,我总是使用 DI 进行单元测试和集成测试。

        如果没有 DI,一切都很难编码。使用Guice Inject or Spring Autowired。就像我下面的测试代码:

        @RunWith(SpringJUnit4ClassRunner.class)
        @ContextConfiguration(locations = "/application-context.xml")
        public class When_inexists_user_disabled {
            @Autowired
            IRegistrationService registrationService;
        
            private int userId;
        
            @Before
            public void setUp() {
                Logger.getRootLogger().setLevel(Level.INFO);
                Logger.getLogger("org.springframework").setLevel(Level.WARN);
                BasicConfigurator.configure();
        
                userId = 999;
            }
        
            @Test(expected=UserNotFoundException.class)
            public void user_should_have_disabled() throws UserNotFoundException {
                registrationService.disable(userId);
            }
        
        }
        

        【讨论】:

        • 我个人认为这更难解决,因为我需要查看应用程序上下文文件以找出 IRegistrationService 正在使用的内容,它是否采用任何模拟或存根以及它们是如何设置的。如果一个测试感觉手动编码太难,那么这表明您可能测试太多,或者您的对象可能需要太多才能开始。
        猜你喜欢
        • 2018-05-15
        • 1970-01-01
        • 1970-01-01
        • 2012-12-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-01-14
        相关资源
        最近更新 更多