Как смоделировать HTTPSession / FlexSession с TestNG и некоторыми Mocking Framework - PullRequest
1 голос
/ 16 января 2011

Я занимаюсь разработкой веб-приложения, работающего на Tomcat 6, с 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;
    }
}    

Метод нуждается в доступе к FlexContext, который существует только при запуске его в контейнере сервлета (не беспокойтесь, есливы не знаете Flex, это больше вопрос о Java-Mocking в целом).В противном случае я получаю исключение Nullpointer при вызове session.setAttribute().К сожалению, я не могу установить FlexContext извне, что позволило бы мне установить его из моих тестов.Он только что получен внутри метода.

Как лучше всего протестировать этот метод с помощью среды Mocking, не изменяя метод или класс, который включает метод?И какой фреймворк был бы самым легким для этого варианта использования (вряд ли есть другие вещи, которые мне приходится высмеивать в моем приложении, это довольно просто)?

Извините, что смогу опробовать все из них для себя и посмотреть, как я могу заставить это работать, но я надеюсь, что я получу быстрый старт с некоторыми хорошими советами!

Ответы [ 2 ]

2 голосов
/ 16 января 2011

Благодаря Дереку Кларксону я успешно издевался над FlexContext, делая логин тестируемым.К сожалению, это возможно только с JUnit, насколько я вижу (тестировал все версии TestNG безуспешно - javaagent JMockit не любит 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");
    }
}

Для дальнейшего тестирования я теперь должен сам реализовать такие вещи, как карта сеанса.Но это нормально, так как мое приложение и мои тестовые примеры довольно просты.

2 голосов
/ 16 января 2011

Очевидно, что один из подходов заключается в том, чтобы изменить его таким образом, чтобы вы могли внедрять такие вещи, как FlexContext.Однако, это не всегда возможно.Некоторое время назад команда, в которой я участвовал, попала в ситуацию, когда нам пришлось смоделировать некоторые вещи внутреннего класса, к которым у нас не было доступа (например, ваш контекст).В итоге мы использовали API под названием jmockit , который позволяет эффективно макетировать отдельные методы, включая статические вызовы.

Используя эту технологию, мы смогли обойти очень грязную реализацию сервера, и вместо того, чтобы развернуть ее на живых серверах и в тесте черного ящика, мы смогли провести модульное тестирование на хорошем уровне, переопределив серверную технологию, котораябыло эффективно жестко закодировано.

Единственная рекомендация, которую я хотел бы сделать по поводу использования чего-то вроде jmockit, - убедиться, что в вашем тестовом коде есть четкая документация и отделение jomockit от вашей основной среды разработки ( easymock или mockito были бы моими рекомендациями).В противном случае вы рискуете запутать разработчиков в отношении различных обязанностей каждой части головоломки, что обычно приводит к некачественным тестам или тестам, которые работают не так хорошо.В идеале, как мы и сделали, оберните код 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();
}

}
...