Как мы пишем модульные тесты для методов, которые включают соединение с БД? - PullRequest
6 голосов
/ 17 июля 2011

У меня был запрос на написание модульных тестов для веб-методов, который фактически связывается с базой данных и возвращает некоторое значение.

Скажем, например, у меня есть веб-сервис с именем "StudentInfoService". Эта веб-служба предоставляет API "getStudentInfo (studentid)"

Вот пример кода

public class StudentInfoService
{
    public StudentInfo getStudentInfo(long studentId) {
            //Communicates with DB and creates
            // StudentInfo object with necessary information
            // and returns it to the caller.
    }
}

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

Ответы [ 4 ]

4 голосов
/ 17 июля 2011

Во-первых, класс StudentInfoService в вашем примере не поддается тестированию или, по крайней мере, не так просто.Это по очень простой причине - невозможно передать объект соединения с базой данных классу, по крайней мере, не в том методе, который вы перечислили.

Чтобы сделать класс тестируемым, потребуется сборкаВаш класс следующим образом:

public class StudentInfoService
{
    private Connection conn;

    public StudentInfoService(Connection conn)
    {
        this.conn = conn;
    }

    public StudentInfo getStudentInfo(long studentId) {
            //Uses the conn object to communicate with DB and creates
            // StudentInfo object with necessary information
            // and returns it to the caller.
    }
}

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

Внедрение зависимостей будетПозвольте вашим тестовым сценариям создать соединение с базой данных (которая является соавтором для вашего класса / тестируемой системы) вместо того, чтобы заставить сам класс / систему создавать объекты соавтора.Проще говоря, вы отделяете механизм установления соединений с базой данных от вашего класса.Если ваш класс ранее просматривал источник данных JNDI, а затем создавал соединение, то это было бы непроверенным, если бы вы не развернули его в контейнере, используя Apache Cactus или подобную инфраструктуру, такую ​​как Arquillian или если вы использовали встроенный контейнер.Изолировав задачу создания соединения от класса, вы теперь можете свободно создавать соединения в своих модульных тестах вне класса и предоставлять их классу по мере необходимости, что позволяет запускать тесты в среде Java SE.

Это позволит вам использовать ориентированную на базу данных инфраструктуру модульного тестирования, такую ​​как DbUnit , которая позволит вам настроить базу данных в известном состоянии перед каждым тестом, а затем передать соединениеStudentInfoService класса, а затем утверждают состояние класса (а также коллаборатора, то есть базы данных) после теста.

Следует подчеркнуть, что когда вы тестируете свои классы модульно, только ваши классы должныбыть единственными тестируемыми системами.Такие объекты, как соединения и источники данных, являются просто соавторами, которые можно и нужно высмеивать.Некоторые модульные тесты используют базы данных в памяти, такие как H2 , HSQL или Derby для модульных тестов, и используют производственные установки баз данных для интеграции ифункциональное тестирование.

2 голосов
/ 17 июля 2011

Попробуйте использовать http://www.dbunit.org/intro.html.

Основная идея - создать базу данных-заглушку с известным набором данных для запуска ваших тестов и подтверждения результатов. Вам потребуется перезагрузить набор данных перед запуском, чтобы восстановить исходное состояние.

1 голос
/ 17 июля 2011

Мы используем базу данных HSQL в памяти. Это очень быстро и совместимо с SQL-92. Для того чтобы наши запросы PostgreSQL выполнялись на HSQL, мы переписываем запросы, используя самописный тест SessionFactory (Hibernate). Преимущества перед реальной базой данных:

  1. намного быстрее, что важно для юнит-тестов
  2. не требует настройки
  3. работает везде, включая наш сервер непрерывной интеграции
0 голосов
/ 17 июля 2011

При работе с «устаревшим кодом» может быть сложно написать модульные тесты без некоторого уровня рефакторинга. При написании объектов я стараюсь придерживаться SOLID . Как часть SOLID, «D» обозначает инверсию зависимости.

Проблема с унаследованным кодом заключается в том, что у вас уже может быть множество клиентов, которые используют конструктор no arg StudentInfoService, что может затруднить добавление конструктора, который принимает параметр Connection conn.

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

public class StudentInfoService {
    private final Connection conn;

    /**
     * This no arg constructor will automatically establish a connection for you. This
     *  will remain around to support legacy code that depends on a no arg constructor.
     */
    public StudentInfoService() throws Exception {
        conn = new ConcreteConnectionObject( ... );
    }

    /**
     * This constructor may be used by your unit tests (or new code).
     */
    public StudentInfoService( Connection conn ) {
        this.conn = conn;
    }

    public StudentInfo getStudentInfo() {
        // this method will need to be slightly refactored to use
        // the class variable "conn" instead of establishing its own connection inline.
    }

}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...