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

发布于 2024-10-12 02:32:41 字数 974 浏览 2 评论 0原文

我正在开发一个在 Tomcat 6 上运行的 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,该方法仅当我在 Servlet 容器上运行它时才存在(如果您不了解 Flex,请不要打扰,它更重要)一般是 Java 模拟问题)。否则,在调用 session.setAttribute() 时,我会收到 Nullpointer 异常。 不幸的是,我无法从外部设置 FlexContext,这将使我能够从测试中设置它。它只是在方法内部获得的。

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

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

I'm developing a web application running on Tomcat 6, with Flex as Frontend. I'm testing my backend with TestNG. Currently, I'm trying to test the following method in my Java-Backend:

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;
    }
}    

The method needs access to the FlexContext which only exists when i run it on the Servlet container (don't bother if you don't know Flex, it's more a Java-Mocking question in general). Otherwise i get a Nullpointer exception when calling session.setAttribute().
Unfortunately, I cannot set the FlexContext from outside, which would make me able to set it from my tests. It's just obtained inside the method.

What would be the best way to test this method with a Mocking framework, without changing the method or the class which includes the method? And which framework would be the easiest for this use case (there are hardly other things i have to mock in my app, it's pretty simple)?

Sorry I could try out all of them for myself and see how i could get this to work, but i hope that i'll get a quickstart with some good advices!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

定格我的天空 2024-10-19 02:32:41

显而易见的一种方法是以一种允许您注入 FlexContext 等内容的方式对其进行重构。然而这并不总是可能的。不久前,我所在的一个团队遇到了一种情况,我们必须模拟一些我们无法访问的内部类内容(例如您的上下文)。我们最终使用了一个名为 jmockit 的 api,它允许您有效地模拟单个方法,包括静态调用。

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

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

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

  1. 有能力注入自己的模拟以由方法返回。
  2. 杀死一个正在寻找正在运行的服务器的构造函数。
  3. 无需访问源代码即可执行上述操作。

这是代码:

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);
    }

}

这是我们用作测试父级的抽象类。

public abstract class AbstractSCAUnitTest extends TestCase {

protected void setUp() throws Exception {
    super.setUp();
    ServiceManagerInterceptor.INSTANCE.clearRegistry();
    ServiceManagerInterceptor.interceptServiceManagerCalls();
}

protected void tearDown() throws Exception {
    ServiceManagerInterceptor.restoreServiceManagerCalls();
    super.tearDown();
}

}

Obvious one approach is to re-factor it in a way that lets you inject things like the FlexContext. However this is not always possible. Some time ago a team I was part of hit a situation where we had to mock out some internal class stuff that we didn't have access to (like your context). We ended up using an api called jmockit which allows you to effective mock individual methods, including static calls.

Using this technology we where able to get around a very messy server implementation and rather than having to deploy to live servers and black box test, we were able to unit test at a fine level by overriding the server technology that was effective hard coded.

The only recommendation I would make about using something like jmockit is to ensure that in your test code there is clear documentation and seperation of jomockit from you main mocking framework (easymock or mockito would be my recommendations). Otherwise you risk confusing developers about the various responsibilities of each part of the puzzle, which usually leads to poor quality tests or tests that don't work that well. Ideally, as we ended up doing, wrap the jmockit code into you testing fixtures so the developers don't even know about it. Dealing with 1 api is enough for most people.

Just for the hell of it, here's the code we used to fix testing for an IBM class. WE basically need to do two things,

  1. Have the ability to inject out own mocks to be returned by a method.
  2. Kill off a constructor that went looking for a running server.
  3. Do the above without having access to the source code.

Here's the code:

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);
    }

}

And here's the abstract class we used as a parent for tests.

public abstract class AbstractSCAUnitTest extends TestCase {

protected void setUp() throws Exception {
    super.setUp();
    ServiceManagerInterceptor.INSTANCE.clearRegistry();
    ServiceManagerInterceptor.interceptServiceManagerCalls();
}

protected void tearDown() throws Exception {
    ServiceManagerInterceptor.restoreServiceManagerCalls();
    super.tearDown();
}

}
情愿 2024-10-19 02:32:41

感谢 Derek Clarkson,我成功模拟了 FlexContext,使登录可测试。不幸的是,据我所知,只有 JUnit 才可能实现(测试了 TestNG 的所有版本均未成功 - JMockit javaagent 不喜欢 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("[email protected]", "asdfasdf");
    }
}

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

Thanks to Derek Clarkson, I successfully mocked the FlexContext, making the login testable. Unfortunately, it's only possible with JUnit, as far as i see (tested all versions of TestNG with no success - the JMockit javaagent does not like TestNG, See this and this issues).

So this is how i'm doing it now:

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("[email protected]", "asdfasdf");
    }
}

For further testing i now have to implement things like the session map myself. But thats okay as my app and my test cases are pretty simple.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文