Салют! Как дела?
Хотел немного подучится чему-то. Искал на хабре в хабе «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), 1);
}
Мы немного не дописали, но это не страшно. Еще успеем. Сейчас надо научится запускать наши тесты.
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 реализован метод — «табличка». А как он работает? Создаем значит, метод _data(), в нем проводим пару нехитрых операций, а потом загружаем все это макросом QFETCH(). Сейчас как раз время увидеть это все на практике!
Теперь пора добавить в test_smart.cpp реализацию нашей «таблички»:
void Test_Smart::min_data()
{
QTest::addColumn<int>("first");
QTest::addColumn<int>("second");
QTest::addColumn<int>("result");
QTest::newRow("data_1") << 1 << 0 << 0;
QTest::newRow("data_1") << -1 << 1 << -1;
QTest::newRow("data_1") << 4 << 8 << 4;
QTest::newRow("data_1") << 0 << 0 << 0;
QTest::newRow("data_1") << 1 << 1 << 1;
QTest::newRow("data_1") << -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-приложения. Что я могу вам сказать? Все советы и пожелания для улучшения статьи прошу написать в комментариях — для меня это важно, так как это, надеюсь, не последняя моя статья.
Удачи и хорошого вам кода;).
Автор: namespace