
gollum подметил что в тексте картинки есть ошибка
Салют, хабр!
Хотел немного подучится чему-то. Искал на хабре в хабе «Qt Software» хоть какой-то пост про юнит-тестирование в Qt. Не нашел. Тут я расскажу базовые вещи про юнит-тестирование на Qt (не ожидайте могучего шаманства). На самом деле, юнит-тестить в Qt довольно просто. Что бы узнать как это делать, приглашаю читать дальше.
Я постараюсь разбить все на части. Погрупирую, так сказать. Начнем.
Теория
Если вы знаете теорию юнит-тестирования — можете пропустить этот пункт.
У нас есть код. Как бы мы этого не хотели в нем есть баги. Баги — это плохо. Чтобы багов не было, нужно писать очень качественный код (это не в этой статье) и главное, тестировать его. Но мы пишем код, дополняем, рефакторим… И каждый раз поддавать каждую версию проекта одному и тому же набору тестов — неприятно. И тут один очень мудрый программист додумался сделать такую программу, которая могла бы тестировать вашу программу этим, заветным набором тестов. И эта модель тестирования называется — юнит-тестинг!
Unit-testing в Qt
А теперь конкретнее. В Qt за юнит-тестирование отвечает модуль QTestLib (testlib). Он предоставляет нам набор макросов для тестирования. Но об этом позже. Есть несколько методов проведения тестов:
- Завести тестовый проект в дочерней директории вашего проекта и тестировать в нем.
- Тестировать макросом qExec(..) в основном проекте
Я чаще использую первый метод, второй — уродлив. Но сегодня я вам покажу на примере второго метода, а первый метод распишу сейчас.
Qt использует прикольную модель: один проект — один тест. Поэтому реализовываются тесты созданием проекта tests в дочерней директории tests основного проекта. В tests лежит класс реализующий тест основного класса. Принцип работы его вы узнаете позже, а основное отличие этого подхода лежит в способе запуска теста. Этот подход требует отсутствие main.cpp и наличие макроса Q_TEST_MAIN(Test_ClassName) в конце test_classname.cpp.
Задача
Предлагаю для примера, реализовать класс Smart, который будет работать с сравнением целых чисел. Что он конкретно будет делать? Реализуем метод int min(int, int), который будет возвращать меньшее число и int max(int, int), который вернет большее число.
Ну давайте уже!
Так. Заходим в Qt Creator. Создаем консольное приложение Qt. Добавляем модуль testlib и gui (надо для тестирования GUI) к .pro-файлу. Теперь можно начинать. Принято начинать с написания тестов, а потом уже самого класса, но я пожалуй отклонюсь от традиций. Будем писать класс Smart. Вам повезло, напишу его я. Вам надо только понять как он работает. Вот этот красавец:
smart.h
#ifndef SMART_H #define SMART_H #include <QObject> #include <QStringList> class Smart : public QObject { Q_OBJECT public: explicit Smart(QObject *parent, const QStringList& list); public slots: int max(int a, int b); int min(int a, int b); }; #endif // SMART_H
smart.cpp
#include "smart.h" Smart::Smart(QObject *parent, const QStringList& list) : QObject(parent) { } int Smart::max(int a, int b) { if(a > b) return a; return b; } int Smart::min(int a, int b) { if(a < b) return a; return b; }
Тестирование QObject* классов
Класс готов. Самое время проверить как он работает! Для этого напишем класс который будет тестировать наш «умный» класс. Он будет называется Test_Smart.
test_smart.h
#ifndef TEST_SMART_H #define TEST_SMART_H #include <QObject> class Test_Smart : public QObject { Q_OBJECT public: explicit Test_Smart(QObject *parent = 0); private slots: // должны быть приватными void max(); // int max(int, int) }; #endif // TEST_SMART_H
test_smart.cpp
#include <QTest> #include "test_smart.h" #include "smart.h" Test_Smart::Test_Smart(QObject *parent) : QObject(parent) { } void Test_Smart::max() { Smart a; QCOMPARE(a.max(1, 0), 1); QCOMPARE(a.max(-1, 1), 1); QCOMPARE(a.max(4, 8), 8); QCOMPARE(a.max(0, 0), 0); QCOMPARE(a.max(1, 1), 1); QCOMPARE(a.max(-10,-5), -5); }
Мы немного не дописали, но это не страшно. Еще успеем. Сейчас надо научится запускать наши тесты.
main.cpp
#include <QApplication> #include <QTest> #include <iostream> #include <cstdlib> #include <cstdio> #include "test_smart.h" using namespace std; int main(int argc, char *argv[]) { freopen("testing.log", "w", stdout); QApplication a(argc, argv); QTest::qExec(new Test_Smart, argc, argv); return 0; }
Компилируем…
testing.log
********* Start testing of Test_Smart ********* Config: Using QTest library 4.8.1, Qt 4.8.1 PASS : Test_Smart::initTestCase() PASS : Test_Smart::max() PASS : Test_Smart::cleanupTestCase() Totals: 3 passed, 0 failed, 0 skipped ********* Finished testing of Test_Smart *********
Поверьте, это — самый лучший исход тестирования!
Но мы еще не протестировали один метод. Я его оставил, так как хочу показать на нем один прием тестирования. Я называю его просто — "табличка". Суть этого метода в том, чтобы не повторять код. Помните наш тестовый метод void max()? Там мы много раз повторяли один и тот же самый код (разве что с разными параметрами). Чтобы этого избежать, в Qt реализован метод — «табличка». А как он работает? Создаем метод method_data(), в нем проводим пару нехитрых операций, а потом загружаем все это макросом QFETCH(). Сейчас как раз время увидеть это все на практике!
Теперь пора добавить в test_smart.cpp реализацию нашей «таблички»:
void Test_Smart::min_data() { QTest::addColumn<int>("first"); QTest::addColumn<int>("second"); QTest::addColumn<int>("result"); QTest::newRow("min_data_1") << 1 << 0 << 0; QTest::newRow("min_data_2") << -1 << 1 << -1; QTest::newRow("min_data_3") << 4 << 8 << 4; QTest::newRow("min_data_4") << 0 << 0 << 0; QTest::newRow("min_data_5") << 1 << 1 << 1; QTest::newRow("min_data_6") << -10 << -5 << -10; } void Test_Smart::min() { Smart a; QFETCH(int, first); QFETCH(int, second); QFETCH(int, result); QCOMPARE(a.min(first, second), result); }
Теперь опять компилируем. Получаем вывод.
testing.log
********* Start testing of Test_Smart ********* Config: Using QTest library 4.8.1, Qt 4.8.1 PASS : Test_Smart::initTestCase() PASS : Test_Smart::max() PASS : Test_Smart::min() PASS : Test_Smart::cleanupTestCase() Totals: 4 passed, 0 failed, 0 skipped ********* Finished testing of Test_Smart *********
Теперь где-нибудь что-то неправильно сделаем. Например поменяем в Smart::min(..) поменяем < на >.
testing.log
********* Start testing of Test_Smart ********* Config: Using QTest library 4.8.1, Qt 4.8.1 PASS : Test_Smart::initTestCase() PASS : Test_Smart::max() FAIL! : Test_Smart::min(data_1) Compared values are not the same Actual (a.min(first, second)): 1 Expected (result): 0 Loc: [test_smart.cpp(41)] FAIL! : Test_Smart::min(data_1) Compared values are not the same Actual (a.min(first, second)): 1 Expected (result): -1 Loc: [test_smart.cpp(41)] FAIL! : Test_Smart::min(data_1) Compared values are not the same Actual (a.min(first, second)): 8 Expected (result): 4 Loc: [test_smart.cpp(41)] FAIL! : Test_Smart::min(data_1) Compared values are not the same Actual (a.min(first, second)): -5 Expected (result): -10 Loc: [test_smart.cpp(41)] PASS : Test_Smart::cleanupTestCase() Totals: 3 passed, 4 failed, 0 skipped ********* Finished testing of Test_Smart *********
Значит все хорошо).
Тестируем GUI
Иногда, а иногда даже очень часто, нам приходится тестировать графический интерфейс. В QTestLib это тоже реализовано. Давайте протестируем QLineEdit.
Вот как выглядит наш test_qlineedit.h:
#ifndef TEST_QLINEEDIT_H #define TEST_QLINEEDIT_H #include <QObject> class Test_QLineEdit : public QObject { Q_OBJECT private slots: // должны быть приватными void edit(); }; #endif // TEST_QLINEEDIT_H
А вот как выглядит, тоже наш test_qlineedit.cpp:
#include <QtTest> #include <QtGui> #include "test_qlineedit.h" void Test_QLineEdit::edit() { QLineEdit a; QTest::keyClicks(&a, "abCDEf123-"); QCOMPARE(a.text(), QString("abCDEf123-")); QVERIFY(a.isModified()); }
Пора поправить main.cpp:
#include <QApplication> #include <QTest> #include <iostream> #include <cstdlib> #include <cstdio> #include "test_smart.h" #include "test_qlineedit.h" using namespace std; int main(int argc, char *argv[]) { freopen("testing.log", "w", stdout); QApplication a(argc, argv); QTest::qExec(new Test_Smart, argc, argv); cout << endl; QTest::qExec(new Test_QLineEdit, argc, argv); return 0; }
Теперь запускаем тестирование:
********* Start testing of Test_Smart ********* Config: Using QTest library 4.8.1, Qt 4.8.1 PASS : Test_Smart::initTestCase() PASS : Test_Smart::max() PASS : Test_Smart::min() PASS : Test_Smart::cleanupTestCase() Totals: 4 passed, 0 failed, 0 skipped ********* Finished testing of Test_Smart ********* ********* Start testing of Test_QLineEdit ********* Config: Using QTest library 4.8.1, Qt 4.8.1 PASS : Test_QLineEdit::initTestCase() PASS : Test_QLineEdit::edit() PASS : Test_QLineEdit::cleanupTestCase() Totals: 3 passed, 0 failed, 0 skipped ********* Finished testing of Test_QLineEdit *********
Вот мы и научились тестировать GUI. Тест показал что QLineEdit работает корректно)).
Аргументы тестирования
| Опция | Объяснение |
|---|---|
| -o filename | Выведет результаты тестирования в файл filename |
| -silent | Ограничить сообщения показом только предупреждений и ошибок |
| -v1 | Отображать информацию о входе и и выходе тестовых методов |
| -v2 | Дополняет опцию -v1 тем, что выводит сообщения для макросов QCOMPARE и QVERIFY |
| -vs | Отображать каждый высланный сигнал и вызванный слот |
| -xml | Осуществлять вывод всей информации в формате XML |
| -eventdelay ms | Заставляем тест остановиться и подождать ms миллисекунд. Эта опция полезна для нахождения ошибок в элементах GUI |
Всего того, что я вам сегодня расказал, точно хватит чтобы прямо сейчас начать тестировать свои Qt-приложения. Что я могу вам сказать? Все советы и пожелания для улучшения статьи прошу написать в комментариях — для меня это важно, так как это, надеюсь, не последняя моя статья.
Удачи и хорошого вам кода;)