我正在开发一个在Tomcat 6上运行的Web应用程序,Flex作为Frontend。我正在用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;
}
}
该方法需要访问FlexContext,该FlexContext仅在我在Servlet容器上运行时才存在(如果您不了解Flex,请不要打扰,一般来说,这更像是一个Java模拟问题)。否则,在调用session.setAttribute()
时,我会收到Nullpointer异常。
不幸的是,我无法从外部设置FlexContext,这使我能够从我的测试中设置它。它刚刚在方法中获得。
使用Mocking框架测试此方法的最佳方法是什么,而不更改方法或包含该方法的类?哪个框架对于这个用例最简单(我的应用程序中几乎没有其他东西可以模拟,这很简单)?
对不起,我可以亲自尝试所有这些,看看我是如何让这个工作的,但我希望我能得到一些快速入门的建议!
答案 0 :(得分:2)
显而易见的一种方法是以一种允许你注入像FlexContext这样的东西的方式对其进行重新分解。然而,这并不总是可行的。前段时间我参与的一个团队遇到了一个我们不得不模拟一些我们无法访问的内部类的东西(比如你的上下文)。我们最终使用了一个名为jmockit的api,它允许您有效地模拟单个方法,包括静态调用。
使用这项技术,我们能够绕过一个非常凌乱的服务器实现,而不是必须部署到实时服务器和黑盒测试,我们能够通过覆盖有效的服务器技术在一个很好的水平单元测试编码。
我对使用像jmockit这样的东西的唯一建议是确保在你的测试代码中有明确的文档,并从你的主要模拟框架中分离jomockit(easymock或mockito将是我的建议)。否则,您可能会使开发人员对拼图的每个部分的各种责任感到困惑,这通常会导致质量不佳的测试或测试效果不佳。理想情况下,正如我们最终做的那样,将jmockit代码包装到测试装置中,以便开发人员甚至不知道它。处理1 api对大多数人来说已经足够了。
仅仅是为了它,这是我们用于修复IBM类测试的代码。我们基本上需要做两件事,
以下是代码:
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();
}
}
答案 1 :(得分:2)
感谢Derek Clarkson,我成功地模拟了FlexContext,使登录可以测试。不幸的是,只有JUnit可以实现(据我所知)(测试所有版本的TestNG都没有成功 - JMockit javaagent不喜欢TestNG,请参阅this和this问题)。
所以这就是我现在正在做的事情:
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");
}
}
为了进一步测试,我现在必须自己实现会话映射之类的东西。但这可以,因为我的应用程序和我的测试用例非常简单。