1

我正在开发一个在 Tomcat 6 上运行的 Web 应用程序,使用 Flex 作为前端。我正在使用 TestNG 测试我的后端。目前,我正在尝试在我的 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;
    }
}    

该方法需要访问仅当我在 Servlet 容器上运行它时才存在的 FlexContext(如果您不了解 Flex,请不要打扰,这通常是一个 Java-Mocking 问题)。否则我在调用时会收到 Nullpointer 异常session.setAttribute()。不幸的是,我无法从外部设置 FlexContext,这将使我能够从我的测试中设置它。它只是在方法内部获得的。

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

抱歉,我可以自己尝试所有这些,看看我怎样才能让它工作,但我希望我能得到一些好的建议的快速入门!

4

2 回答 2

2

显而易见的一种方法是重新分解它,让您可以注入 FlexContext 之类的东西。然而,这并不总是可能的。前段时间,我所在的一个团队遇到了一种情况,我们不得不模拟一些我们无法访问的内部类的东西(比如你的上下文)。我们最终使用了一个名为jmockit的 api ,它允许您有效地模拟单个方法,包括静态调用。

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

关于使用 jmockit 之类的东西,我唯一的建议是确保在您的测试代码中有清晰的文档,并且将 jomockit 与您的主要模拟框架分开(我的建议是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();
}

}
于 2011-01-16T10:45:18.743 回答
2

感谢 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("asdf@asdf.de", "asdfasdf");
    }
}

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

于 2011-01-16T12:29:06.857 回答