Что unit test писать для класса с использованием дженериков в Java?

Взять очень конкретный пример класса JpaDao, определенного в этом article:

public abstract class JpaDao<K, E> implements Dao<K, E> {
    protected Class<E> entityClass;

    @PersistenceContext
    protected EntityManager entityManager;

    public JpaDao() {
        ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();
        this.entityClass = (Class<E>) genericSuperclass.getActualTypeArguments()[1];
    }

    public void persist(E entity) { entityManager.persist(entity); }

    public void remove(E entity) { entityManager.remove(entity); }

    public E findById(K id) { return entityManager.find(entityClass, id); }
}

было бы лучше написать модульные тесты для всех существующих сущностей в приложении (Order, Customer, Book и т.д.), или было бы приемлемо писать модульные тесты только для одного объекта, как намечено этим другим вопросом? Есть ли какая-либо лучшая практика в отношении модульных тестов java-классов с использованием дженериков?

Ответы

Ответ 1

Вы можете написать абстрактный класс тестов для объектов, которые подклассы этого.

Например:

public abstract class JpaDaoTest<K,E> {

    abstract protected E getEntity();
    abstract protected JpaDao getDAO();

    @Test
    public void testPersistCreatesEntity()
    {
         JpaDao dao = getDAO();
         dao.persist(getEntity());
         // assert
     }
}

Контракт, который вы реализуете для универсальных классов, должен быть протестирован так же, как и общий, предполагая, что getEntity() правильно устанавливает и реляционные зависимости.

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

Ответ 2

Если использование другого типа объекта вызывает выполнение другого кода, вам нужен отдельный тестовый пример.

Я бы тестировал столько, сколько мог, в общем наборе тестов, которые используют только один тип сущности. Если большая часть вашего кода относится ко всем объектам одинаково, тогда нет необходимости тестировать его более одного раза. Я бы установил отдельные тестовые примеры для какого-либо особого поведения, которое требовало бы, чтобы у определенных сущностей DAO было другое поведение.

Ответ 3

Из FAQ JUnit:

4) При каких условиях следует проверять методы get() и set()?

Модульные тесты предназначены для облегчения страха, что что-то может сломаться. Если вы считаете, что метод get() или set() может разумно ломаться или на самом деле способствовал дефекту, то обязательно напишите тест.

Короче, проверяйте, пока вы не уверены. То, что вы выбираете для тестирования, является субъективным, исходя из вашего опыта и уровня уверенности. Не забывайте быть практичным и максимизировать инвестиции в тестирование.

В нем также говорится:

"Испытай, пока страх не превратится в скуку".

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

Ответ 4

Как и BalusC, я рекомендую тестировать конкретные реализации. Причиной этого является то, что он соответствует принципу "Ты не нужен". Добавьте достаточно тестов, чтобы использовать прецедент, который вы пытаетесь реализовать. Затем, добавив больше вариантов использования, добавьте дополнительные модульные тесты.

Ответ 5

Если мне нужно будет проверить поведение класса относительно семантики его типа, я бы пошел на проверку инвариантов типа. Другими словами, попробуйте установить некоторые утверждения, которые верны для комбинации типов all, а не только те, которые вы ожидаете использовать, но любой тип во Вселенной, в том числе те, которые еще не изобретены. Например:

private <K, E> void testTypes(K k, E e) {
    JpaDao<K, E> dao = new JpaDaoImpl<K, E>();

    dao.persist(e);

    assertEquals(dao.getById(e.getId()).getClass(), e.getClass());
}

@Test
public void testIntegerAndOrder() {
    this.<Integer, Order>testTypes(10, new Order());
}

См., независимо от того, какие типы K и E есть, предполагается, что утверждения будут выполняться (метод testIntegerAndOrder() проверяет это утверждение с использованием конкретных значений типа).

Это, конечно, следует использовать в сочетании с модульными тестами, которые фактически проверяют поведение для некоторых конкретных значений переменных типа. Это были бы те же самые единичные тесты, которые вы могли бы найти в любом учебнике JUnit. Что-то в духе:

@Test
public void testDao() throws Exception {
    JpaDao<Integer, Order> dao = getDao();

    Order order = ...;
    order.setId(10);

    dao.persist(order);

    assertEquals(order, dao.findById(10));
}

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

Ответ 6

Протестируйте конкретный класс, чтобы избежать проблемы переопределения его позже.

Это красиво делать общие тесты, но небезопасно.

Вы можете CopyPaste большую часть Unit Test во время разработки, а затем вы можете настроить тесты.