Страницы

Поиск по вопросам

среда, 1 января 2020 г.

JUnit тесты для DataService

#java #юнит_тесты


Как можно это все проверить? Мои мысли:

Внести фейковые данные в базу данных.
Добавить данные через тест.
Задать значения для проверки.
Сравнить данные для проверки с данными из БД?

Как это примерно реализовать? Просто с дата сервисами не работал особо вот и возникло
затруднение.
public class NewsDataService {
    private News news;
    private List newss;
    private DatabaseConnector db;
    private ResultSet result;

    public NewsDataService() throws ClassNotFoundException, SQLException {
        db = new DatabaseConnector();
        newss = new ArrayList();

    }

    public News getNews(int id) throws SQLException {
        result = db.query("SELECT * FROM news WHERE idnews = " + id);
        result = db.query("SELECT * FROM news WHERE idstudent = " + id);
        result = db.query("SELECT * FROM news WHERE idgroup = " + id);
        while (result.next()) {
            news.setDate(result.getString("date"));
            news.setTitle(result.getString("title"));
            news.setContent(result.getString("content"));
        }
        return news;

    }

    public News findByTitleNews(String title) throws SQLException {
        result = db.query("SELECT * FROM news WHERE title ='" + title + "'");
        while (result.next()) {
            news.setTitle(result.getString("title"));
            news.setId(result.getInt("idnews"));
            news.setId(result.getInt("idgroup"));
            news.setId(result.getInt("idstudent"));
            news.setDate(result.getString("date"));
            news.setContent(result.getString("content"));
        }
        return news;

    }

    public List findAllNews() throws SQLException {
        result = db.query("SELECT * FROM news");
        while (result.next()) {
            news.setTitle(result.getString("title"));
            news.setId(result.getInt("idnews"));
            news.setId(result.getInt("idgroup"));
            news.setId(result.getInt("idgroup"));
            news.setDate(result.getString("date"));
            news.setContent(result.getString("content"));

        }

        return newss;

    }

    public void save(News news) throws SQLException {
        String sql = "INSERT INTO news('title', 'content', 'date') "
                + "VALUES ('" + news.getTitle() + "', '" + news.getContent()
                + "', '" + news.getDate() + "',?);";

        PreparedStatement pstmt = db.getConnect().prepareStatement(sql);
        pstmt.execute();
        pstmt.close();
        System.out.println(sql);
        System.out.println("News successful inserted");
    }
    }
    


Ответы

Ответ 1



Протестировать текущий вариант класса NewsDataService - не самая тривиальная задача для решения, попробую объяснить почему. В процессе написания данного класса вы решили заинкапсулировать всю логику работы с БД внутри кода, дойдя до крайне низкого уровня - ручного составления SQL запросов. И теперь ваш класс служит крайне узкоспециализированной цели, поскольку он умеет только лишь создаваться и отвечать на запросы вида "дай мне сущность с id" и "сохрани такую-то сущность". Чтобы улучшить этот аспект кода (coupling), я бы порекомендовал бы вам почитать про Inversion of Control, Dependency Injection и про ORM. Я могу предложить 3 различных подхода к тестированию текущего варианта реализации NewsDataService, которые вполне как можно скомбинировать в тех или иных пропорциях Воспользуйтесь sandbox-based подходом - возьмите легковесную базу данных, заполните ее реальными значениями и напишите юнит-тесты без моков вообще. Вообще говоря, если бы вы писали такие тесты перед написанием кода (следуя методологии TDD), то вам бы сразу открылись многие минусы и неудобства использования написанного класса. Специально для таких целей можно использовать DbUnit, позволяющий упростить создание сендбоксовой базы данных и написание Setup / Teardown методов. Следуя принципам IoC, инициализируйте ваш DatabaseConnector в другом месте, а в NewsDataService прокидывайте его через конструктор. Это, во-первых, снимает с NewsDataService ответственность за инициализацию подключения, а во-вторых упрощает тестирование. Вот здесь уже сам по себе DatabaseConnector можно подменить mock-объектом, который будет эмулировать часть функциональности, например, на любой запрос SELECT будет возвращать предопределенную пачку новостей. Подход не самый лучший, потому что mock-объект должен быть предельно простым (иначе потребуются тесты на тесты), а этот факт сильно сковывает руки при тестировании. То есть, грубо говоря, вы не можете и не должны парсить запрос в вашем mock'e, поэтому максимум, что вы можете делать с его помощью - это разделять запросы SELECT / UPDATE, возвращая на них какую-нибудь заранее заданную чушь, ну и, например, проверять, что databaseConnector.query вообще был вызван. Общее правило достаточно простое - если код mock'a вызывает вопросы о том, правильно ли он работает для всех случаев, то это - плохой mock. Иногда, естественно, бывают ситуации, что без сложного mock'a не обойтись, но в таком случае нужно писать тесты на этот самый mock, что обычно бывает нежелательным. Выделить генерацию SQL запросов в отдельный класс типа SqlQueryProvider. В таком случае ваш текущий код начнет меняться примерно в следующую сторону: ... databaseConnector.query(sqlQueryProvider.getQueryToRetrieveById(...)); Такой подход уже позволяет отдельно покрыть тестами генерацию запросов, и, зная, что запросы к генерируются корректно, серьзно сэкономить на реальном тестировании NewsDataService. Опять же, для тестирования NewsDataService в таком случае может пригодиться mock на SqlQueryProvider. Несколько референсов по теме: Unit Testing Database Code Testing With DbUnit How do I unit test jdbc code in java? Всегда, кстати, хотелось посмотреть, как авторы советов типа "возьмите XYZMock, да заmock'айте эту обертку над БД и протестите, делов то" делают это на практике и сделали бы это, достанься им legacy код типа NewsDataService.

Ответ 2



все зависит от поставленной задачи. если ваша задача проверить работу БД тогда для этого можно воспользоватся одним из уже существующих фреймворков. для субд оракл: ounit, plunit, pluto, utplsql (этот самый мощний) если вам нужно проверить работу программы ( хотя здесь вы должны уже быть уверены что СУБД работает правильно ). тогда вам следует придерживатся того плана который вы уже наметили. как реализовать - сначала создаете массив обьектов с тестовыми данными (можно опустить этот пункт но так проще незапутатся в данных). потом используете их для формирования запросов вставки данных в БД. и непосредственно во время теста, - выбираете все данные и проверяете только те обьекты которые вы создали во время теста, - сравниваете их с теми которые вы выбрали. они должны быть равны между собой (можно сравнивать по полям. можно для этого перегрузить метод equals внутри этих обьектов) но начать стоит с того чтоб исправить ошибки в коде: 1) private List newss - это должно быть локальной переменной в методе (во всех где используется). 2) private ResultSet result; - это должно быть локальной переменной в методе (во всех где используется) 3) PreparedStatement pstmt = db.getConnect().prepareStatement(sql); - вот эту переменную наоборот имеет смысл вынести за пределы метода и сделать полем. для того чтоб можно было использовать несколько раз. В самом же методе надос делать так: if(null == pstmt) { pstmt = db.getConnect().prepareStatement(sql); } вот вы в данном коде использовали PreparedStatement но если смотреть на остальной код то стает что вы не разобрались как это работает. Каждый раз когды вы отсылаете какой-то запрос СУБД она его сначала парсит. Эта операция очень важная и дорогостоющая, - потому что парсер у всей СУБД только один. Посему если слать много разных запросов паралельно то ваша СУБД будет работать очень медленно. Для того чтоб избежать однотипных запросов есть специальные средства для подстановки параметров. в JDBC это PreparedStatement. пример: String sql = "SELECT * FROM movies WHERE year_made = ?"; PreparedStatement ps = connection.prepareStatement(sql); ps.setInt(1,2002); ResultSet rs = ps.executeQuery(); // очистить установленные параметры можно так (необходимо для работы в цикле): ps.clearParameters();

Ответ 3



Чтобы проверить работу вашего класса в независимости от БД, стоит воспользоваться такой концепцией как Mock Objects, и заменить в тесте базу на мок. Почитайте об этом более подробно, статей достаточно. Из библиотек могу посоветовать EasyMock.

Комментариев нет:

Отправить комментарий