Russian Qt Forum
Ноябрь 23, 2024, 15:26 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1]   Вниз
  Печать  
Автор Тема: Пошаговая инструкция. Разработка ПО на Qt C++ по методике TDD  (Прочитано 19560 раз)
8Observer8
Гость
« : Январь 24, 2014, 20:00 »

Внимание! Эта тема перетекла, в следующую тему, в шапку которой я буду добавлять изменения/добавления. Вам достаточно будет подписаться на неё. О всех измененияx/добавленияx я буду извещать: http://www.prog.org.ru/topic_26944_0.html

Пошаговая инструкция. Разработка ПО на Qt C++ по методике TDD (test-driven development)

Инструментарий:

- Qt 5.2.0 for Windows 32-bit (MinGW 4.8, OpenGL, 689 MB)
- Компилятор: MinGW 4.8
- Операционная система: Windows 7

Введение:

Принцип TDD (test-driven development - разработка ПО через тестирование) - сначала пишем тесты для пустого модуля (модуль - это функция или класс), а потом реализацию для этого модуля.

Такой способ разработки позволяет:

- описывать функциональность до написания самой функции
- демонстрировать, что функциональность реализована, как это и требовалось (заказчику, пользователю и\или самому себе)
- проводить рефакторинг (усовершенствовать код с целью оптимизации и/или для улучшения читабельности) без опасения нарушения функциональности

Инструкция:

- скачиваем и устанавливаем "Qt 5.2.0 for Windows 32-bit (MinGW 4.8, OpenGL, 689 MB)" http://qt-project.org/downloads

- запускаем Qt Creator

- если у Вас Qt Creator на русском, то я рекомендовал бы переключиться на английский. Для этого выбираем в меню "Инструменты" -> "Параметры..."

- в окне "Параметры" слева выбираем "Среда" -> открываем вкладку "Основные" -> выбираем в выпадающем списке "English" -> нажимаем кнопку "OK" -> перезапускаем Qt Creator

Допустим у нас задание: разработать класс с названием MyDate, в котором будет один метод с названием nameOfMonth(). Этот метод принимает число от 1 до 12 и возвращает название месяца. Если входной параметр метода не входит в диапазан [1, 12], то метод вернёт строку "Error: incorrect input data"

- в меню Qt Creator'а выбираем "File" -> "New File or Project..."

- в разделе "Projects" выбираем "Other Project" -> во второй колонке выбираем "Qt Unit Test"

- нажимаем кнопку "Choose"

- в поле "Name" вводим: TestMyDate

- нажимаем "Next" -> "Next" -> "Next" -> "Finish"

- нажимаем правой кнопкой мыши по проекту в разделе "Projects" -> нажимаем "Add New...", как показано на рисунке:



- в разделе "Files and Clases" выбираем "C++" -> во втором разделе выбираем "C++ Class"

- нажимаем кнопку "Choose"

- в поле "Class name" вводим: MyDate

- нажимаем кнопку "Next" -> нажимаем кнопку "Finish"

- открываем файл "mydate.h" и меняем его содержимое на следующее:

mydate.h
Код:
#ifndef MYDATE_H
#define MYDATE_H

#include <QString>

class MyDate
{
public:
    QString nameOfMonth(int n);
};

#endif // MYDATE_H

- нажимаем правой кнопкой по функции nameOfMonth() -> выбираем "Refactor" -> выбираем "Add Definition in myclass.cpp", как показано на рисунке:



- меняем содержимое файла "mydate.cpp" на следующее:

mydate.cpp
Код:
#include "mydate.h"

QString MyDate::nameOfMonth(int n)
{
    return "";
}

- открываем файл "tst_testmydatetest.cpp" и меняем его содержимое на следующее:

tst_testmydatetest.cpp
Код:
#include <QString>
#include <QtTest>
#include "mydate.h"

class TestMyDateTest : public QObject
{
    Q_OBJECT

public:
    TestMyDateTest();

private Q_SLOTS:
    void testCase1_data();
    void testCase1();
};

TestMyDateTest::TestMyDateTest()
{
}

void TestMyDateTest::testCase1_data()
{
    QTest::addColumn<int>("n");
    QTest::addColumn<QString>("expected");

    QTest::newRow("nameOfMonth01") << 1 << "January";
    QTest::newRow("nameOfMonth02") << 2 << "February";
    QTest::newRow("nameOfMonth03") << 3 << "March";
    QTest::newRow("nameOfMonth04") << 4 << "April";
    QTest::newRow("nameOfMonth05") << 5 << "May";
    QTest::newRow("nameOfMonth06") << 6 << "June";
    QTest::newRow("nameOfMonth07") << 7 << "July";
    QTest::newRow("nameOfMonth08") << 8 << "August";
    QTest::newRow("nameOfMonth09") << 9 << "September";
    QTest::newRow("nameOfMonth10") << 10 << "October";
    QTest::newRow("nameOfMonthe11") << 11 << "November";
    QTest::newRow("nameOfMonth12") << 12 << "December";

    QTest::newRow("nameOfMonthe13") << 0 << "Error: incorrect input data";
    QTest::newRow("nameOfMonth14") << -1 << "Error: incorrect input data";
    QTest::newRow("nameOfMonth15") << 13 << "Error: incorrect input data";
}

void TestMyDateTest::testCase1()
{
    MyDate md;

    QFETCH(int, n);
    QFETCH(QString, expected);

    QString actual = md.nameOfMonth(n);
    QCOMPARE(actual, expected);
}

QTEST_APPLESS_MAIN(TestMyDateTest)

#include "tst_testmydatetest.moc"

И так, рассмотрим кратко, что мы изменили в файле "tst_testmydatetest.cpp":

- добавили метод testCase1_data() в класс "TestMyDateTest" для инициализации данных

Данные инициализируются так:

Код:
QTest::newRow("nameOfMonth01") << 1 << "January";
QTest::newRow("nameOfMonth02") << 2 << "February";
QTest::newRow("nameOfMonth03") << 3 << "March";
   
Первыми идут параметры тестируемого метода, а последним - ожидаемый результат.

- добавили функцию сравнения ожидаемого результата и результата, который возвращает тестируемый метод:

Код:
void TestMyDateTest::testCase1()
{
    MyDate md;

    QFETCH(int, n);
    QFETCH(QString, expected);

    QString actual = md.nameOfMonth(n);
    QCOMPARE(actual, expected);
}

- нажимаем Ctrl+R для построения и запуска программы

- в окне "Application Output" мы можем видеть, что не один из вызовов тестируемого метода не завершился положительно:



- пишем функционал nameOfMonth(), для этого меняем содержимое файла "mydate.cpp" на следующее:

Код:
#include "mydate.h"

QString MyDate::nameOfMonth(int n)
{
    QString month;

    switch (n) {
    case 1:
        month = "January";
        break;
    case 2:
        month = "February";
        break;
    case 3:
        month = "March";
        break;
    case 4:
        month = "April";
        break;
    case 5:
        month = "May";
        break;
    case 6:
        month = "June";
        break;
    case 7:
        month = "July";
        break;
    case 8:
        month = "August";
        break;
    case 9:
        month = "September";
        break;
    case 10:
        month = "October";
        break;
    case 11:
        month = "November";
        break;
    case 12:
        month = "December";
        break;
    default:
        month = "Error: incorrect input data";
        break;
    }

    return month;
}

- нажимаем Ctrl+R для построения и запуска программы

Теперь мы видим, что тесты пройдены успешно:



P.S. Подробнее о TDD можно почитать здесь: ru.wikipedia.org/wiki/Разработка_через_тестирование

Добавил 07.04.2014 12:41

Пример разработки ПО через тестирование

Задание на разработку.. Написать класс с именем FiveAndFive. В этом классе должен быть метод fiveAndFive(), который возвращает квадрат своего аргумента.

Соглашения вызова метода:
1) Метод работает с аргументом из диапазона: [5, 4*10^5]. Если нарушено это соглашение, то метод выбрасывает исключение.
2) Метод работает с аргументом кратным пяти. Если нарушено это соглашение, то метод выбрасывает исключение.

- создаём консольное приложение "Qt Console Application"
Имя папки: FiveAndFiveProject
Имя проекта: FiveAndFive

- создаём проект "Qt Unit Test" в той же папке: FiveAndFiveProject
Имя проекта: FiveAndFiveTests
Имя класса с тестами: FiveAndFiveTests
Имя исходного файла с тестами: tst_FiveAndFiveTests.cpp

- в проекте FiveAndFive создаём класс с тем же именем: FiveAndFive, а так же создаём заглушку для метода fiveAndFive(). Так же добавляем классы исключений (код проекта см. ниже)

Весь код проекта FiveAndFive: https://github.com/8Observer8/FiveAndFive
Весь код проекта FiveAndFiveTests: https://github.com/8Observer8/FiveAndFiveTests

Добавил новую информацию 4/24/2014 8:45 AM

Для пользовательских типов надо, чтобы фреймворк поддерживал Mock-объекты: http://ru.wikipedia.org/wiki/Mock-%D0%BE%D0%B1%D1%8A%D0%B5%D0%BA%D1%82

TDD на Западе очень популярен. Microsoft даже включил его в Visual Studio, начиная с версии 2012:


Причём они реализовали TDD так, что можно создавать классы и методы (загрушки) прямо из тестов. Таким образом, сначала пишем тесты, а потом открываем файл с классами и начинаем писать реализацию для методов (часто запуская тесты), пока вся полоса не будет зелёной. Вот здесь на примере можно понять философию методологии "Разработка через тестирование": http://msdn.microsoft.com/en-us/library/hh212233.aspx

К сожалению, QTest в Qt не поддерживает Mock-объекты, поэтому придётся изучать фреймворки "Google Mock" и\или "Boost Mock". На данном этапе, QTest вполне устраивает. Смог обойти проблему, что QTest не поддерживает тестирование исключений и сравнение вещественных чисел с дельтой. Для демонстрации есть пример:
- проект: https://github.com/8Observer8/FiveAndFive
- проект "Qt Unit Test": https://github.com/8Observer8/FiveAndFiveTests

В CppUnit это есть:

Сравнение вещественных чисел с дельтой:
Код
C++ (Qt)
CPPUNIT_ASSERT_DOUBLES_EQUAL_MESSAGE( message, expected, actual, delta );
 

Тестирование на выброс исключения:
Код
C++ (Qt)
CPPUNIT_ASSERT_THROW_MESSAGE( message, expression, ExceptionType );
 

Пример из фреймворка CppUnit:
Код
C++ (Qt)
std::vector<int> v;
CPPUNIT_ASSERT_THROW_MESSAGE( "- std::vector<int> v;", v.at( 50 ), std::out_of_range );
 

Подробно о TDD написано в книгах:
- Мартин Фаулер - Рефакторинг. Улучшение существующего кода
- Кент Бек. Экстремальное программирование. Разработка через тестирование
- Р. Мартин. Быстрая разработка программного обеспечения. Принципы, практика, примеры (в примерах использованы языки C++ и Java)
- Мартин Р.С., Мартин М. - Принципы, паттерны и методики гибкой разработки на языке C# - 2011
« Последнее редактирование: Июнь 26, 2014, 09:04 от 8Observer8 » Записан
kambala
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4747



Просмотр профиля WWW
« Ответ #1 : Январь 24, 2014, 20:14 »

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

Изучением C++ вымощена дорога в Qt.

UTF-8 has been around since 1993 and Unicode 2.0 since 1996; if you have created any 8-bit character content since 1996 in anything other than UTF-8, then I hate you. © Matt Gallagher
xintrea
Moderator
Супер активный житель
*****
Offline Offline

Сообщений: 754



Просмотр профиля WWW
« Ответ #2 : Январь 26, 2014, 22:07 »

на форуме свой блог не ведут. для того, чтобы написать как создать юнит тест, больше половины статьи можно выкинуть.
Нормальная статья. Мне например, как человеку, которому приходится пользоваться Qt только время от времени, вместо того чтоб разбираться где какие опции крутить чтоб сделать то что никогда раньше не делал, проще воспользоваться такой статьей со скриншотами и описанием "для тупых". Статья еще ценна тем, что актуальна, ибо речь идет про Qt 5.2.
Записан

Собираю информацию по крупицам
http://webhamster.ru
kambala
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4747



Просмотр профиля WWW
« Ответ #3 : Январь 26, 2014, 23:01 »

сейчас статья уже исправлена (ненужная информация засунута в тэг кода в самом начале)
Записан

Изучением C++ вымощена дорога в Qt.

UTF-8 has been around since 1993 and Unicode 2.0 since 1996; if you have created any 8-bit character content since 1996 in anything other than UTF-8, then I hate you. © Matt Gallagher
8Observer8
Гость
« Ответ #4 : Январь 27, 2014, 12:27 »

Тестирование методов класса, которые возвращают вещественные числа

Следующий пример отличается от того, что в инструкции тем, что в этом, тестируемых методов два и они возвращают вещественные числа. Тестируем методы "расчёт_прощади" "расчёт_объёма" класса "Сфера".

К сожалению, в Qt нет макросов для сравнения вещественных чисел с заданной точностью, наподобие, к примеру, макроса из CppUnit (который сравнивает ожидаемый результат (expected) и результат работы функции (actual) с заданой точностью дельта (delta)):

Код:
CPPUNIT_ASSERT_DOUBLES_EQUAL(expected, actual, delta);

Поэтому будем использовать свою функцию, реализацию которой мне подсказали на этом форуме год назад: http://www.qtcentre.org/threads/52904-QtTest-How-to-compare-double-numbers?highlight=

Код:
static inline bool qFuzzyCompare(double p1, double p2, double delta)
{
    return (qAbs(p1 - p2) <= delta * qMin(qAbs(p1), qAbs(p2)));
}

Все файлы проекта TestShere:

sphere.h
Код:
#ifndef SPHERE_H
#define SPHERE_H

class Sphere
{
public:
    double calcArea(double radius);
    double calcVolume(double radius);
};

#endif // SPHERE_H

sphere.cpp
Код:
#include "sphere.h"
#include <cmath>

double Sphere::calcArea(double radius) {
    double area = 4.0 * M_PI * pow(radius, 2.0);
    return area;
}

double Sphere::calcVolume(double radius) {
    double volume = (4.0/3.0) * M_PI * pow(radius, 3.0);
    return volume;
}

tst_testspheretest.cpp
Код:
#include <QString>
#include <QtTest>
#include "sphere.h"
#include <QDebug>

class TestSphereTest : public QObject
{
    Q_OBJECT

public:
    TestSphereTest();

    static inline bool qFuzzyCompare(double p1, double p2, double delta)
    {
        return (qAbs(p1 - p2) <= delta * qMin(qAbs(p1), qAbs(p2)));
    }

private Q_SLOTS:
    void testCalcArea_data();
    void testCalcArea();

    void testCalcVolume_data();
    void testCalcVolume();
};

TestSphereTest::TestSphereTest()
{
}

void TestSphereTest::testCalcArea_data()
{
    QTest::addColumn<double>("radius");
    QTest::addColumn<double>("expected");

    QTest::newRow("calcArea01") << 6.0 << 452.3893;
    QTest::newRow("calcArea02") << 7.0 << 615.7527;
    QTest::newRow("calcArea03") << 8.0 << 804.2477;
}

void TestSphereTest::testCalcArea()
{
    Sphere s;

    QFETCH(double, radius);
    QFETCH(double, expected);

    double actual = s.calcArea(radius);
    double delta = 0.0001;
    bool result = qFuzzyCompare(actual, expected, delta);
    QVERIFY2(result, "Failure");
}

void TestSphereTest::testCalcVolume_data()
{
    QTest::addColumn<double>("radius");
    QTest::addColumn<double>("expected");

    QTest::newRow("calcVolume01") << 6.0 << 904.7787;
    QTest::newRow("calcVolume02") << 7.0 << 1436.7550;
    QTest::newRow("calcVolume03") << 8.0 << 2144.6606;
}

void TestSphereTest::testCalcVolume()
{
    Sphere s;

    QFETCH(double, radius);
    QFETCH(double, expected);

    double actual = s.calcVolume(radius);
    double delta = 0.0001;
    bool result = qFuzzyCompare(actual, expected, delta);
    QVERIFY2(result, "Failure");
}

QTEST_APPLESS_MAIN(TestSphereTest)

#include "tst_testspheretest.moc"

TestShere.pro
Код:
QT       += testlib

QT       -= gui

TARGET = tst_testspheretest
CONFIG   += console
CONFIG   -= app_bundle

TEMPLATE = app


SOURCES += tst_testspheretest.cpp \
    sphere.cpp
DEFINES += SRCDIR=\\\"$$PWD/\\\"

HEADERS += \
    sphere.h
« Последнее редактирование: Январь 28, 2014, 10:33 от 8Observer8 » Записан
8Observer8
Гость
« Ответ #5 : Июнь 26, 2014, 09:05 »

В шапку темы добавил текст:

Внимание! Эта тема перетекла, в следующую тему, в шапку которой я буду добавлять изменения/добавления. Вам достаточно будет подписаться на неё. О всех измененияx/добавленияx я буду извещать: http://www.prog.org.ru/topic_26944_0.html
Записан
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.176 секунд. Запросов: 24.