Junit 如何使用TestNG和一些模拟框架模拟HTTPSession/FlexSession

Junit 如何使用TestNG和一些模拟框架模拟HTTPSession/FlexSession,junit,mocking,testng,jmockit,Junit,Mocking,Testng,Jmockit,我正在开发一个运行在Tomcat6上的web应用程序,前端是Flex。我正在用TestNG测试我的后端。目前,我正尝试在Java后端测试以下方法: public class UserDAO extends AbstractDAO { (...) public UserPE login(String mail, String password) { UserPE dbuser = findUserByMail(mail); if (dbuser =

我正在开发一个运行在Tomcat6上的web应用程序,前端是Flex。我正在用TestNG测试我的后端。目前,我正尝试在Java后端测试以下方法:

public class UserDAO extends AbstractDAO {
    (...)
    public UserPE login(String mail, String password) {
        UserPE dbuser = findUserByMail(mail); 
        if (dbuser == null || !dbuser.getPassword().equals(password))
            throw new RuntimeException("Invalid username and/or password");
        // Save logged in user
        FlexSession session = FlexContext.getFlexSession();
        session.setAttribute("user", dbuser);
        return dbuser;
    }
}    
该方法需要访问FlexContext,而FlexContext只有在我在Servlet容器上运行时才存在(如果您不了解Flex,就不用麻烦了,一般来说,这更像是一个Java模拟问题)。否则,在调用
session.setAttribute()
时,会出现Nullpointer异常。 不幸的是,我无法从外部设置FlexContext,这将使我能够从测试中设置它。它是在方法内部获得的

在不改变包含该方法的方法或类的情况下,用模拟框架测试该方法的最佳方法是什么?对于这个用例,哪种框架最简单(我的应用程序中几乎没有其他东西需要模仿,非常简单)


很抱歉,我可以自己尝试所有这些,看看如何让它发挥作用,但我希望我能得到一些好建议的快速入门

显而易见的一种方法是以一种可以注入FlexContext之类内容的方式重新考虑它。然而,这并不总是可能的。不久前,我所在的一个团队遇到了这样的情况:我们必须模拟一些我们无法访问的内部类内容(比如您的上下文)。我们最终使用了一个api,该api允许您有效地模拟各个方法,包括静态调用

使用这项技术,我们能够绕过非常混乱的服务器实现,而不必部署到实时服务器和黑盒测试,我们能够通过覆盖有效硬编码的服务器技术,在良好的级别上进行单元测试

关于使用类似jmockit的东西,我唯一的建议是确保在测试代码中有清晰的文档,并将jomockit与主模拟框架分开(或者是我的建议)。否则,您可能会让开发人员对拼图的每个部分的各种责任感到困惑,这通常会导致质量低下的测试或工作不太好的测试。理想情况下,正如我们最后所做的那样,将jmockit代码包装到您的测试装置中,这样开发人员甚至都不知道它。对于大多数人来说,处理1个api就足够了

这是我们用来修复IBM类测试的代码。我们基本上需要做两件事

  • 有能力注入自己的mock,由方法返回
  • 杀死一个寻找正在运行的服务器的构造函数
  • 在没有访问源代码的情况下执行上述操作
  • 代码如下:

    import java.util.HashMap;
    import java.util.Map;
    
    import mockit.Mock;
    import mockit.MockClass;
    import mockit.Mockit;
    
    import com.ibm.ws.sca.internal.manager.impl.ServiceManagerImpl;
    
    /**
     * This class makes use of JMockit to inject it's own version of the
     * locateService method into the IBM ServiceManager. It can then be used to
     * return mock objects instead of the concrete implementations.
     * <p>
     * This is done because the IBM implementation of SCA hard codes the static
     * methods which provide the component lookups and therefore there is no method
     * (including reflection) that developers can use to use mocks instead.
     * <p>
     * Note: we also override the constructor because the default implementations
     * also go after IBM setup which is not needed and will take a large amount of
     * time.
     * 
     * @see AbstractSCAUnitTest
     * 
     * @author Derek Clarkson
     * @version ${version}
     * 
     */
    
    // We are going to inject code into the service manager.
    @MockClass(realClass = ServiceManagerImpl.class)
    public class ServiceManagerInterceptor {
    
        /**
         * How we access this interceptor's cache of objects.
         */
        public static final ServiceManagerInterceptor   INSTANCE                = new ServiceManagerInterceptor();
    
        /**
         * Local map to store the registered services.
     */
        private Map<String, Object>                         serviceRegistry = new HashMap<String, Object>();
    
        /**
         * Before runnin your test, make sure you call this method to start
         * intercepting the calls to the service manager.
         * 
         */
        public static void interceptServiceManagerCalls() {
            Mockit.setUpMocks(INSTANCE);
        }
    
        /**
         * Call to stop intercepting after your tests.
         */
        public static void restoreServiceManagerCalls() {
            Mockit.tearDownMocks();
        }
    
        /**
         * Mock default constructor to stop extensive initialisation. Note the $init
         * name which is a special JMockit name used to denote a constructor. Do not
         * remove this or your tests will slow down or even crash out.
         */
        @Mock
        public void $init() {
            // Do not remove!
        }
    
        /**
         * Clears all registered mocks from the registry.
         * 
         */
        public void clearRegistry() {
            this.serviceRegistry.clear();
        }
    
        /**
         * Override method which is injected into the ServiceManager class by
         * JMockit. It's job is to intercept the call to the serviceManager's
         * locateService() method and to return an object from our cache instead.
         * <p>
         * This is called from the code you are testing.
         * 
         * @param referenceName
         *           the reference name of the service you are requesting.
         * @return
         */
        @Mock
        public Object locateService(String referenceName) {
            return serviceRegistry.get(referenceName);
        }
    
        /**
         * Use this to store a reference to a service. usually this will be a
         * reference to a mock object of some sort.
         * 
         * @param referenceName
         *           the reference name you want the mocked service to be stored
         *           under. This should match the name used in the code being tested
         *           to request the service.
         * @param serviceImpl
         *           this is the mocked implementation of the service.
         */
        public void registerService(String referenceName, Object serviceImpl) {
            serviceRegistry.put(referenceName, serviceImpl);
        }
    
    }
    

    显然,一种方法是以一种允许注入FlexContext之类的东西的方式重新考虑它。然而,这并不总是可能的。不久前,我所在的一个团队遇到了这样的情况:我们必须模拟一些我们无法访问的内部类内容(比如您的上下文)。我们最终使用了一个api,该api允许您有效地模拟各个方法,包括静态调用

    使用这项技术,我们能够绕过非常混乱的服务器实现,而不必部署到实时服务器和黑盒测试,我们能够通过覆盖有效硬编码的服务器技术,在良好的级别上进行单元测试

    关于使用类似jmockit的东西,我唯一的建议是确保在测试代码中有清晰的文档,并将jomockit与主模拟框架分开(或者是我的建议)。否则,您可能会让开发人员对拼图的每个部分的各种责任感到困惑,这通常会导致质量低下的测试或工作不太好的测试。理想情况下,正如我们最后所做的那样,将jmockit代码包装到您的测试装置中,这样开发人员甚至都不知道它。对于大多数人来说,处理1个api就足够了

    这是我们用来修复IBM类测试的代码。我们基本上需要做两件事

  • 有能力注入自己的mock,由方法返回
  • 杀死一个寻找正在运行的服务器的构造函数
  • 在没有访问源代码的情况下执行上述操作
  • 代码如下:

    import java.util.HashMap;
    import java.util.Map;
    
    import mockit.Mock;
    import mockit.MockClass;
    import mockit.Mockit;
    
    import com.ibm.ws.sca.internal.manager.impl.ServiceManagerImpl;
    
    /**
     * This class makes use of JMockit to inject it's own version of the
     * locateService method into the IBM ServiceManager. It can then be used to
     * return mock objects instead of the concrete implementations.
     * <p>
     * This is done because the IBM implementation of SCA hard codes the static
     * methods which provide the component lookups and therefore there is no method
     * (including reflection) that developers can use to use mocks instead.
     * <p>
     * Note: we also override the constructor because the default implementations
     * also go after IBM setup which is not needed and will take a large amount of
     * time.
     * 
     * @see AbstractSCAUnitTest
     * 
     * @author Derek Clarkson
     * @version ${version}
     * 
     */
    
    // We are going to inject code into the service manager.
    @MockClass(realClass = ServiceManagerImpl.class)
    public class ServiceManagerInterceptor {
    
        /**
         * How we access this interceptor's cache of objects.
         */
        public static final ServiceManagerInterceptor   INSTANCE                = new ServiceManagerInterceptor();
    
        /**
         * Local map to store the registered services.
     */
        private Map<String, Object>                         serviceRegistry = new HashMap<String, Object>();
    
        /**
         * Before runnin your test, make sure you call this method to start
         * intercepting the calls to the service manager.
         * 
         */
        public static void interceptServiceManagerCalls() {
            Mockit.setUpMocks(INSTANCE);
        }
    
        /**
         * Call to stop intercepting after your tests.
         */
        public static void restoreServiceManagerCalls() {
            Mockit.tearDownMocks();
        }
    
        /**
         * Mock default constructor to stop extensive initialisation. Note the $init
         * name which is a special JMockit name used to denote a constructor. Do not
         * remove this or your tests will slow down or even crash out.
         */
        @Mock
        public void $init() {
            // Do not remove!
        }
    
        /**
         * Clears all registered mocks from the registry.
         * 
         */
        public void clearRegistry() {
            this.serviceRegistry.clear();
        }
    
        /**
         * Override method which is injected into the ServiceManager class by
         * JMockit. It's job is to intercept the call to the serviceManager's
         * locateService() method and to return an object from our cache instead.
         * <p>
         * This is called from the code you are testing.
         * 
         * @param referenceName
         *           the reference name of the service you are requesting.
         * @return
         */
        @Mock
        public Object locateService(String referenceName) {
            return serviceRegistry.get(referenceName);
        }
    
        /**
         * Use this to store a reference to a service. usually this will be a
         * reference to a mock object of some sort.
         * 
         * @param referenceName
         *           the reference name you want the mocked service to be stored
         *           under. This should match the name used in the code being tested
         *           to request the service.
         * @param serviceImpl
         *           this is the mocked implementation of the service.
         */
        public void registerService(String referenceName, Object serviceImpl) {
            serviceRegistry.put(referenceName, serviceImpl);
        }
    
    }
    

    多亏了Derek Clarkson,我成功地模拟了FlexContext,使登录可以测试。不幸的是,据我所知,只有JUnit才有可能做到这一点(测试了所有版本的TestNG,但没有成功——JMockitJavaAgent不喜欢TestNG,请参阅和发布)

    我现在就是这样做的:

    public class MockTests {
        @MockClass(realClass = FlexContext.class)
        public static class MockFlexContext {
            @Mock
            public FlexSession getFlexSession() {
                System.out.println("I'm a Mock FlexContext.");
                return new FlexSession() {
    
                    @Override
                    public boolean isPushSupported() {
                        return false;
                    }
    
                    @Override
                    public String getId() {
                        return null;
                    }
                };
            }
        }
    
        @BeforeClass
        public static void setUpBeforeClass() throws Exception {
            Mockit.setUpMocks(MockFlexContext.class);
            // Test user is registered here
            (...)
        }
    
        @Test
        public void testLoginUser() {
            UserDAO userDAO = new UserDAO();
            assertEquals(userDAO.getUserList().size(), 1);
            // no NPE here 
            userDAO.login("asdf@asdf.de", "asdfasdf");
        }
    }
    

    为了进一步测试,我现在必须自己实现会话映射之类的东西。但这没关系,因为我的应用程序和测试用例都非常简单。

    多亏了Derek Clarkson,我成功地模拟了FlexContext,使登录可以测试。不幸的是,据我所知,只有JUnit才有可能做到这一点(测试了所有版本的TestNG,但没有成功——JMockitJavaAgent不喜欢TestNG,请参阅和发布)

    我现在就是这样做的:

    public class MockTests {
        @MockClass(realClass = FlexContext.class)
        public static class MockFlexContext {
            @Mock
            public FlexSession getFlexSession() {
                System.out.println("I'm a Mock FlexContext.");
                return new FlexSession() {
    
                    @Override
                    public boolean isPushSupported() {
                        return false;
                    }
    
                    @Override
                    public String getId() {
                        return null;
                    }
                };
            }
        }
    
        @BeforeClass
        public static void setUpBeforeClass() throws Exception {
            Mockit.setUpMocks(MockFlexContext.class);
            // Test user is registered here
            (...)
        }
    
        @Test
        public void testLoginUser() {
            UserDAO userDAO = new UserDAO();
            assertEquals(userDAO.getUserList().size(), 1);
            // no NPE here 
            userDAO.login("asdf@asdf.de", "asdfasdf");
        }
    }
    

    为了进一步测试,我现在必须自己实现会话映射之类的东西。但这没关系,因为我的应用程序和测试用例都非常简单。

    当然,注入会很好,但在我当前的环境中是不可能的。谢谢你的观点,现在我有一个方向要走了!我现在就试试JMockit,糟糕透了。关于JMockit和TestNG似乎存在一些问题:您是如何在测试中解决这些问题的?我猜你在用JUnit?是的,JUnit。当我们做这个项目时,我们使用easymock作为我们的模拟框架。一旦我通过编写抽象的父测试类将jmockit从developers视图中删除,团队中关于如何进行单元测试的困惑就少了很多。我从来没有使用过TestNG,所以我不能把它作为一个测试框架来评论,只能说我遇到了更多使用JUnit Experience的开发人员