Pull to refresh

Юнит-тестирование в Qt

Reading time 6 min
Views 79K

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-приложения. Что я могу вам сказать? Все советы и пожелания для улучшения статьи прошу написать в комментариях — для меня это важно, так как это, надеюсь, не последняя моя статья.

Удачи и хорошого вам кода;)
Tags:
Hubs:
+27
Comments 21
Comments Comments 21

Articles