С концепцией разработки через тестирование (TDD - test-driven development) я познакомился по книгам Роберта Мартина:
Это действительно хорошие книги, которые следует прочитать, если вы намереваетесь серьезно заниматься программированием или уже это делаете, но по какой-то причине обошли эти книги стороной. В какой-то мере отношение к TDD у Роберта М. доведено до фанатизма. Однако нельзя не согласиться, что преимущества от применения этой техники весьма существенны. Но есть и свои недостатки, которые во многом связаны с ограничениями разработки через тестирование. Об этих преимуществах и недостатках в контексте использования Qt мы и поговорим.
Идея создания программ с помощью TDD достаточно проста. Ее можно описать с помощью следующего алгоритма:
Звучит достаточно легко, но нужно приобрести некую сноровку, чтобы пользоваться этим приемом правильно.
Перечислим главные преимущества, которые вы получите от использования TDD:
Очень часто приверженцы TDD умалчивают о его недостатках, но они есть. И с ними нужно считаться. Вот наиболее важные из них:
Random()
? Подумайте об этом в свободное время;А теперь попробуем создать несложный Qt-модуль с помощью TDD.
Если вы поищите примеры использования TDD в интернете или литературе, то увидите, что практически всегда они сводятся к разработке модулей без внешних зависимостей. И в какой-то мере такая ситуация вполне оправдана, поскольку TDD для этого и предназначен (мое личное мнение, хотя вы можете быть не согласны). Например, с помощью TDD легко можно разработать алгоритм разложения целого числа на множители. Вполне понятно, как должна выглядеть соответствующая функция, какие она должна принимать аргументы, и какие тесты для ее проверки понадобятся.
А что вы скажете о разработке сетевого модуля? Или модуля для взаимодействия с базами данных? В этом случае мы имеет внешние зависимости, контроль над которыми ограничен. В большинстве случаев мы не можем воспроизвести редкие ошибки сторонних систем по нашему желанию. И как же поступить? - Для этого используют Mock-объекты. Идея заключается в том, что для тестирования можно использовать не настоящую внешнюю систему, а некую фиктивную реализацию. В простейшем случае такой Mock-объект может возвращать константу. В других же случаях он может использовать упрощенную реализацию функционала реальной подсистемы (например, сохранять данные не в БД, а в ассоциативном массиве в памяти). Еще существуют разновидности Mock-фреймворков, которые позволяют создавать Mock-объекты с произвольными последовательностями возвращаемых значений (например, вас может заинтересовать проект googlemock).
Как я уже отмечал, мое мнение заключается в том, что TDD эффективнее применять для разработки систем без внешних зависимостей. Конечно, Mock-объекты - это выход, но так ли они полезны? С одной стороны, если задуматься о принципе DRY (см. Принцип DRY в действии), то смысл есть. Ведь если вы выявили какую-то ошибку в коде взаимодействия с БД, то вполне вероятно, что она когда-нибудь может повториться вновь. В этом случае у вас три варианта: либо тестировать ее вручную после каждого изменения; либо надеяться, что она больше никогда не произойдет; либо автоматизировать ее проверку с помощью соответствующих модульных тестов. Первый вариант плох тем, что вы будете делать лишнюю работу, то есть повторяться. Второй вариант делает ваш код уязвимым. Последний же вариант потребует довольно много дополнительной работы. На мой взгляд, в этом случае все зависит от масштабов и критичности проекта. Если ошибки недопустимы (например, авиационное или медицинское ПО), то тестирование обязано быть исчерпывающим, а поэтому автоматизированным. С другой стороны, если вы пишите приложение для среднестатистического пользователя, то вполне можете рассчитывать на некий уровень толерантности. Вспомните те же продукты из серии Microsoft Office. Не знаю как у вас, но у меня они регулярно падают и глючат (при этом я пользуюсь лицензионными версиями).
Давайте реализуем с помощью TDD алгоритм для поворота матрицы на 90 градусов по часовой стрелке. Он должен делать примерно следующее:
То есть можно представить, что берется некая матрица и просто переворачивается на бок. Выглядит достаточно легко, поэтому думаю, что мы справимся.
И первым делом подготовим Qt-проект со следующей структурой:
.
├── app.pri
├── bin/
├── common.pri
├── include/
│ ├── tdddemolib_global.h
│ └── tdddemolib.h
├── lib.linux/
├── lib.pri
├── src/
│ └── TDDDemoLib/
│ ├── tdddemolib.cpp
│ └── TDDDemoLib.pro
├── TDDDemo.pro
├── TDDDemo.pro.user
└── tests/
└── TDDDemoTest/
├── TDDDemoTest.pro
└── tst_tdddemotest.cpp
Как вы можете видеть, проект состоит из двух модулей: TDDDemoLib
и TDDDemoTest
. Как понятно из названия, первый модуль представляет собой библиотеку, в которой мы определим и реализуем нашу функцию, а во второй модуль мы поместим наши тесты. Более подробно про организацию Qt-проектов вы можете прочитать в моей заметке Структура Qt-проекта на C++, поэтому здесь я лишь приведу содержимое pro
-файлов без дополнительных комментариев.
TDDemo.pro
:
TEMPLATE = subdirs
SUBDIRS += \
src/TDDDemoLib \
tests/TDDDemoTest
TDDemoLib.pro
:
QT -= gui
include( ../../common.pri )
include( ../../lib.pri )
TARGET = TDDDemoLib$${LIB_SUFFIX}
TEMPLATE = lib
DEFINES += TDDDEMOLIB_LIBRARY
SOURCES += tdddemolib.cpp
HEADERS += ../../include/tdddemolib.h\
../../include/tdddemolib_global.h
TDDemoTest.pro
:
QT += testlib
QT -= gui
TARGET = tst_tdddemotest
CONFIG += console
CONFIG -= app_bundle
TEMPLATE = app
SOURCES += tst_tdddemotest.cpp
include( ../../common.pri )
include( ../../app.pri )
LIBS += -lTDDDemoLib$${LIB_SUFFIX}
Здесь мы лишь обратим внимание на то, что в подпроекте TDDDemoTest
мы подключили Qt-модуль testlib
. С его помощью мы и будем проводить тестирование.
В tdddemolib.h
сейчас мы определим лишь тип матрицы:
#ifndef TDDDEMOLIB_H
#define TDDDEMOLIB_H
#include "tdddemolib_global.h"
#include <QMetaType>
#include <QVector>
typedef QVector< QVector< int > > Matrix;
Q_DECLARE_METATYPE( Matrix )
#endif // TDDDEMOLIB_H
Обратите внимание, что в этом фрагменте мы воспользовались Q_DECLARE_METATYPE
. Это сделано для того, чтобы в коде тестов задействовать одну весьма удобную возможность, о который мы скоро поговорим.
Но раз используем TDD, то в первую очередь займемся тестами. Посмотрим на содержимое tst_tdddemotest.cpp
:
#include <QString>
#include <QtTest>
#include <tdddemolib.h>
class TDDDemoTest : public QObject {
Q_OBJECT
public:
TDDDemoTest();
private slots:
void rotate90DegreesTest();
void rotate90DegreesTest_data();
};
TDDDemoTest::TDDDemoTest() {
}
void TDDDemoTest::rotate90DegreesTest() {
QFETCH( Matrix, matrix );
QFETCH( Matrix, result );
}
void TDDDemoTest::rotate90DegreesTest_data() {
QTest::addColumn< Matrix >( "matrix" );
QTest::addColumn< Matrix >( "result" );
}
QTEST_APPLESS_MAIN( TDDDemoTest )
#include "tst_tdddemotest.moc"
Для организации наших тестов мы будем использовать класс TDDDemoTest
. Он наследует QObject
и имеет два слота: rotate90DegreesTest()
и rotate90DegreesTest_data()
. Это позволит нам строить тесты, управляемые данными. То есть мы всего один раз определим процедуру проверки в rotate90DegreesTest()
, а все данные будем компоновать в соответствующем слоте rotate90DegreesTest_data()
. Определение структур данных, которые будут использоваться в тесте, осуществляется с помощью QTest::addColumn()
. А получение этих данных выполняется с помощью макроса QFETCH
. По сути этот механизм работает на основе QVariant
, поэтому нам и понадобилось объявление мета-типа для Matrix
.
Модульный тест в Qt является исполняемым приложением (в нашем случае консольным), поэтому в нем предусмотрен макрос для определения функции main()
. Этим макросом мы и воспользовались: QTEST_APPLESS_MAIN( TDDDemoTest )
. Кроме того, обратите внимание на последнюю строку. Поскольку весь код теста мы пишем в одном cpp
-файле, то нам нужно явно указать инструкцию #include "tst_tdddemotest.moc"
. Это нужно лишь из-за того, что мы унаследовали класс QObject
и использовали макрос Q_OBJECT
для получения возможности добавления слотов.
Проверку корректности прохождения теста мы будем проверять следующим образом:
void TDDDemoTest::rotate90DegreesTest() {
QFETCH( Matrix, matrix );
QFETCH( Matrix, result );
QCOMPARE( rotate90Degrees( matrix ), result );
}
Это тот же слот, который мы уже рассматривали. Но в него добавлена одна строка. В ней мы используем макрос QCOMPARE
. Он обеспечивает сравнение фактического и ожидаемого результатов работы функции на основе переданных данных. Если они будут отличаться, то тест будет засчитан как не пройденный.
Теперь добавим первые тестовые данные:
void TDDDemoTest::rotate90DegreesTest_data() {
QTest::addColumn< Matrix >( "matrix" );
QTest::addColumn< Matrix >( "result" );
QTest::newRow( "Empty matrix" ) << Matrix { { } } << Matrix { { } };
}
Мы делаем это с помощью QTest::newRow()
. В качестве простейшего теста мы выполняем поворот пустой матрицы. Для краткости записи я использовал синтаксис C++11 при инициализации матриц (то есть векторов). Конечно, можно обойтись и без них, но тогда кода вышло бы существенно больше.
Отлично. Первый тест готов. Но мы не можем даже собрать его, поскольку у нас еще нет функции rotate90Degrees()
. Добавим ее объявление и минимальную реализацию:
// Добавили в tdddemolib.h:
Matrix rotate90Degrees( const Matrix& matrix );
// Добавили в tdddemolib.cpp:
Matrix rotate90Degrees( const Matrix& matrix ) {
return matrix;
}
И вот что мы можем увидеть на консоли после запуска теста:
********* Start testing of TDDDemoTest *********
Config: Using QTest library 4.8.6, Qt 4.8.6
PASS : TDDDemoTest::initTestCase()
PASS : TDDDemoTest::rotate90DegreesTest()
PASS : TDDDemoTest::cleanupTestCase()
Totals: 3 passed, 0 failed, 0 skipped
********* Finished testing of TDDDemoTest *********
Прекрасно. Пора добавлять новые тестовые данные:
void TDDDemoTest::rotate90DegreesTest_data() {
// …
QTest::newRow( "2x1 single value matrix" ) <<
Matrix { { 0, 0 } } <<
Matrix { { 0 },
{ 0 } };
}
Этот вариант соответствует случаю матрицы, состоящей из одной строки, включающей два одинаковых символа. В результате должна получиться матрица-столбец. Попробуем запустить тест:
********* Start testing of TDDDemoTest *********
Config: Using QTest library 4.8.6, Qt 4.8.6
PASS : TDDDemoTest::initTestCase()
FAIL! : TDDDemoTest::rotate90DegreesTest(2x1 single value matrix) Compared values are not the same
Loc: [tst_tdddemotest.cpp(24)]
PASS : TDDDemoTest::cleanupTestCase()
Totals: 2 passed, 1 failed, 0 skipped
********* Finished testing of TDDDemoTest *********
Тест не прошел. Нужно это исправить:
Matrix rotate90Degrees( const Matrix& matrix ) {
if( matrix[ 0 ].isEmpty() ) {
return matrix;
}
return Matrix { { 0 },
{ 0 } };
}
Если вы не знакомы с TDD, то представленный промежуточный алгоритм может показаться вам необычным. Но в этом и заключается идея разработки через тестирование. Не усложняйте реализацию раньше времени. Пишите минимальное количество кода, которое способно обеспечить прохождение всех имеющихся к текущему моменту тестов. Убедимся, что это так, и тесты проходят:
********* Start testing of TDDDemoTest *********
Config: Using QTest library 4.8.6, Qt 4.8.6
PASS : TDDDemoTest::initTestCase()
PASS : TDDDemoTest::rotate90DegreesTest()
PASS : TDDDemoTest::cleanupTestCase()
Totals: 3 passed, 0 failed, 0 skipped
********* Finished testing of TDDDemoTest *********
Отлично. Добавляем новый набор данных:
void TDDDemoTest::rotate90DegreesTest_data() {
// …
QTest::newRow( "2x1 different values matrix" ) <<
Matrix { { 1, 2 } } <<
Matrix { { 1 },
{ 2 } };
}
Теперь у нас матрица 2x1, состоящая из разных элементов. Тест провален. Исправляем код:
Matrix rotate90Degrees( const Matrix& matrix ) {
if( matrix[ 0 ].isEmpty() ) {
return matrix;
}
return Matrix { { matrix[ 0 ][ 0 ] },
{ matrix[ 0 ][ 1 ] } };
}
Теперь тест проходит. Пора добавить новые тестовые данные:
void TDDDemoTest::rotate90DegreesTest_data() {
// …
QTest::newRow( "1x2 different values matrix" ) <<
Matrix { { 1 },
{ 2 } } <<
Matrix { { 2, 1 } };
Теперь в тестовых данных мы используем матрицу 1x2 с разными элементами. Тест вновь не проходит, сообщая об ошибке "index out of range"
. Займемся доработкой функции:
Matrix rotate90Degrees( const Matrix& matrix ) {
if( matrix[ 0 ].isEmpty() ) {
return matrix;
} else if( matrix[ 0 ].size() == 2 ) {
return Matrix { { matrix[ 0 ][ 0 ] },
{ matrix[ 0 ][ 1 ] } };
}
return Matrix { { matrix[ 1 ][ 0 ], matrix[ 0 ][ 0 ] } };
}
Пока что реализация выглядит весьма неуклюже, но тест проходит. Большего нам пока что и не требуется. Пора писать новый набор тестовых данных:
void TDDDemoTest::rotate90DegreesTest_data() {
// …
QTest::newRow( "2x2 different values matrix" ) <<
Matrix { { 1, 2 },
{ 3, 4 } } <<
Matrix { { 3, 1 },
{ 4, 2 } };
}
Теперь мы имеем дело с матрицей 2x2 из разных элементов. Разумеется, тест не проходит. Исправим это:
Matrix rotate90Degrees( const Matrix& matrix ) {
if( matrix[ 0 ].isEmpty() ) {
return matrix;
} else if( matrix[ 0 ].size() == 2 ) {
if( matrix.size() == 2 ) {
return Matrix { { matrix[ 1 ][ 0 ], matrix[ 0 ][ 0 ] },
{ matrix[ 1 ][ 1 ], matrix[ 0 ][ 1 ] } };
}
return Matrix { { matrix[ 0 ][ 0 ] },
{ matrix[ 0 ][ 1 ] } };
}
return Matrix { { matrix[ 1 ][ 0 ], matrix[ 0 ][ 0 ] } };
}
Опять мы использовали самое прямое решение возникшей проблемы. Однако оно работает и теперь тест проходит. Пора снова все испортить и добавить еще один набор тестовых данных:
void TDDDemoTest::rotate90DegreesTest_data() {
// …
QTest::newRow( "3x1 different values matrix" ) <<
Matrix { { 1, 2, 3 } } <<
Matrix { { 1 },
{ 2 },
{ 3 } };
}
В этот раз мы взяли матрицу большей размерности с разными элементами. Наша текущая реализация функции с этим не справится. Требуются доработки:
Matrix rotate90Degrees( const Matrix& matrix ) {
if( matrix[ 0 ].isEmpty() ) {
return matrix;
} else if( matrix[ 0 ].size() == 2 ) {
if( matrix.size() == 2 ) {
return Matrix { { matrix[ 1 ][ 0 ], matrix[ 0 ][ 0 ] },
{ matrix[ 1 ][ 1 ], matrix[ 0 ][ 1 ] } };
}
return Matrix { { matrix[ 0 ][ 0 ] },
{ matrix[ 0 ][ 1 ] } };
} else if( matrix[ 0 ].size() == 3 ) {
return Matrix { { matrix[ 0 ][ 0 ] },
{ matrix[ 0 ][ 1 ] },
{ matrix[ 0 ][ 2 ] } };
}
return Matrix { { matrix[ 1 ][ 0 ], matrix[ 0 ][ 0 ] } };
}
Необходимость в рефакторинге уже достаточно очевидна. Но давайте сначала добавим еще один набор тестовых данных:
void TDDDemoTest::rotate90DegreesTest_data() {
// …
QTest::newRow( "3x2 different values matrix" ) <<
Matrix { { 1, 2, 3 },
{ 4, 5, 6 } } <<
Matrix { { 4, 1 },
{ 5, 2 },
{ 6, 3 } };
}
Мы опять увеличили размер матрицы, добавив к предыдущему случаю еще один ряд. Тест не проходит. Вновь беремся за код функции:
Matrix rotate90Degrees( const Matrix& matrix ) {
if( matrix[ 0 ].isEmpty() ) {
return matrix;
} else if( matrix[ 0 ].size() == 2 ) {
if( matrix.size() == 2 ) {
return Matrix { { matrix[ 1 ][ 0 ], matrix[ 0 ][ 0 ] },
{ matrix[ 1 ][ 1 ], matrix[ 0 ][ 1 ] } };
}
return Matrix { { matrix[ 0 ][ 0 ] },
{ matrix[ 0 ][ 1 ] } };
} else if( matrix[ 0 ].size() == 3 ) {
if( matrix.size() == 2 ) {
return Matrix { { matrix[ 1 ][ 0 ], matrix[ 0 ][ 0 ] },
{ matrix[ 1 ][ 1 ], matrix[ 0 ][ 1 ] },
{ matrix[ 1 ][ 2 ], matrix[ 0 ][ 2 ] } };
}
return Matrix { { matrix[ 0 ][ 0 ] },
{ matrix[ 0 ][ 1 ] },
{ matrix[ 0 ][ 2 ] } };
}
return Matrix { { matrix[ 1 ][ 0 ], matrix[ 0 ][ 0 ] } };
}
Теперь тесты проходят, но закономерности настолько очевидны, что структуру кода без особых сложностей можно упростить. Давайте сделаем это. Но начнем проводить изменения постепенно:
Matrix rotate90Degrees( const Matrix& matrix ) {
const int rowCount = matrix[ 0 ].size();
const int columnCount = matrix.size();
if( rowCount == 0 ) {
return matrix;
} else if( rowCount == 2 ) {
if( columnCount == 2 ) {
return Matrix { { matrix[ 1 ][ 0 ], matrix[ 0 ][ 0 ] },
{ matrix[ 1 ][ 1 ], matrix[ 0 ][ 1 ] } };
}
return Matrix { { matrix[ 0 ][ 0 ] },
{ matrix[ 0 ][ 1 ] } };
} else if( rowCount == 3 ) {
if( columnCount == 2 ) {
return Matrix { { matrix[ 1 ][ 0 ], matrix[ 0 ][ 0 ] },
{ matrix[ 1 ][ 1 ], matrix[ 0 ][ 1 ] },
{ matrix[ 1 ][ 2 ], matrix[ 0 ][ 2 ] } };
}
return Matrix { { matrix[ 0 ][ 0 ] },
{ matrix[ 0 ][ 1 ] },
{ matrix[ 0 ][ 2 ] } };
}
return Matrix { { matrix[ 1 ][ 0 ], matrix[ 0 ][ 0 ] } };
}
В приведенном коде мы просто заметили, что размеры матрицы, которую нужно перевернуть, используются многократно, поэтому определили соответствующие константы rowCount
и columnCount
. Обратите внимание, что они определены для перевернутой, а не исходной матрицы. То есть количество строк стало количеством столбцов и наоборот.
Теперь обратим внимание, что во всех случаях (кроме rowCount == 0
) на i
-ой строке в j
-ом столбце перевернутой матрицы стоит элемент исходной матрицы вида: matrix[ matrix.size() - 1 - j ][ i ]
. Проведем соответствующий рефакторинг:
Matrix rotate90Degrees( const Matrix& matrix ) {
const int rowCount = matrix[ 0 ].size();
const int columnCount = matrix.size();
if( rowCount == 0 ) {
return matrix;
}
Matrix rotatedMatrix( rowCount );
for( int i = 0; i < rowCount; ++i ) {
rotatedMatrix[ i ].resize( columnCount );
for( int j = 0; j < columnCount; ++j ) {
rotatedMatrix[ i ][ j ] = matrix[ columnCount - 1 - j ][ i ];
}
}
return rotatedMatrix;
}
Еще раз запустим тесты и убедимся, что ничего не сломали. Отлично. Все работает. Но мы еще не закончили. Ведь до сих пор мы рассматривали лишь корректные тестовые данные. Теперь необходимо написать тесты для случаев ошибочного использования нашей функции. Добавим в тестовый класс еще два слота:
void rotate90DegreesExceptionTest();
void rotate90DegreesExceptionTest_data();
Организуем их по тому же принципу, что был использован при компоновке тестов на основе допустимых данных. Слот для проверки выполнения условия реализуем следующим образом:
void TDDDemoTest::rotate90DegreesExceptionTest() {
QFETCH( Matrix, matrix );
QFETCH( QString, errorMsg );
try {
rotate90Degrees( matrix );
QFAIL( "Must not reach this place" );
} catch( const std::invalid_argument& e ) {
QCOMPARE( QString::fromStdString( e.what() ), errorMsg );
}
}
В данном случае мы ожидаем, что строка QFAIL( "Must not reach this place" )
не должна достигаться, поскольку произойдет исключение std::invalid_argument
и мы перейдем в соответствующий обработчик, где проверим полученное сообщение об ошибке. Добавим первые тестовые данные:
void TDDDemoTest::rotate90DegreesExceptionTest_data() {
QTest::addColumn< Matrix >( "matrix" );
QTest::addColumn< QString >( "errorMsg" );
QTest::newRow( "Matrix with no columns" ) << Matrix { } << "Matrix must have at least one column";
}
В качестве первого тестового набора данных мы передаем матрицу без столбцов. В этом случае тест не только не проходит, но и завершается с ошибкой выхода за границы массива. Опять возвращаемся к нашей реализации:
Matrix rotate90Degrees( const Matrix& matrix ) {
const int columnCount = matrix.size();
if( columnCount == 0 ) {
throw std::invalid_argument( "Matrix must have at least one column" );
}
const int rowCount = matrix[ 0 ].size();
if( rowCount == 0 ) {
return matrix;
}
Matrix rotatedMatrix( rowCount );
for( int i = 0; i < rowCount; ++i ) {
rotatedMatrix[ i ].resize( columnCount );
for( int j = 0; j < columnCount; ++j ) {
rotatedMatrix[ i ][ j ] = matrix[ columnCount - 1 - j ][ i ];
}
}
return rotatedMatrix;
}
Тест проходит. Добавим еще тестовых данных:
void TDDDemoTest::rotate90DegreesExceptionTest_data() {
// …
QTest::newRow( "Matrix with rows of different sizes" ) << Matrix { { 1 },
{ 2, 3 } } <<
"Matrix must have the rows of the same size";
}
В этом случае мы хотим, чтобы в случае получения матрицы со строками разных размеров наша функция возвращала исключение. Убедимся, что тест не проходит:
********* Start testing of TDDDemoTest *********
Config: Using QTest library 4.8.6, Qt 4.8.6
PASS : TDDDemoTest::initTestCase()
PASS : TDDDemoTest::rotate90DegreesTest()
FAIL! : TDDDemoTest::rotate90DegreesExceptionTest(Matrix with rows of different sizes) Must not reach this place
Loc: [tst_tdddemotest.cpp(77)]
PASS : TDDDemoTest::cleanupTestCase()
Totals: 3 passed, 1 failed, 0 skipped
********* Finished testing of TDDDemoTest *********
Внесем поправки в реализацию нашей функции:
Matrix rotate90Degrees( const Matrix& matrix ) {
const int columnCount = matrix.size();
if( columnCount == 0 ) {
throw std::invalid_argument( "Matrix must have at least one column" );
}
const int rowCount = matrix[ 0 ].size();
if( rowCount == 0 ) {
return matrix;
}
Matrix rotatedMatrix( rowCount );
for( int i = 0; i < rowCount; ++i ) {
rotatedMatrix[ i ].resize( columnCount );
for( int j = 0; j < columnCount; ++j ) {
if( matrix[ columnCount - 1 - j ].size() != rowCount ) {
throw std::invalid_argument( "Matrix must have the rows of the same size" );
}
rotatedMatrix[ i ][ j ] = matrix[ columnCount - 1 - j ][ i ];
}
}
return rotatedMatrix;
}
Здесь мы просто добавили соответствующую проверку, поэтому если во входной матрице обнаружится строка другого размера, то будет выброшено исключение. Запустим тесты еще раз:
********* Start testing of TDDDemoTest *********
Config: Using QTest library 4.8.6, Qt 4.8.6
PASS : TDDDemoTest::initTestCase()
PASS : TDDDemoTest::rotate90DegreesTest()
PASS : TDDDemoTest::rotate90DegreesExceptionTest()
PASS : TDDDemoTest::cleanupTestCase()
Totals: 4 passed, 0 failed, 0 skipped
********* Finished testing of TDDDemoTest *********
Вот теперь можно считать, что мы закончили с реализацией функции поворота матрицы на 90 градусов по часовой стрелке. Но у вас мог возникнуть вопрос: "А зачем было все так усложнять, ведь можно было реализовать алгоритм сразу?". В какой-то мере я с вами согласен. С другой стороны, поскольку мы добавляли тесты постепенно, то в качестве бонуса получили следующее:
Материала получилось достаточно много, поэтому думаю, что на этом мы сейчас и закончим. Однако не могу не упомянуть о том, что в QTestLib
входит довольно много полезных инструментов. Например, с помощью QSignalSpy
вы можете тестировать сигналы; для оценки производительности вы можете использовать QBENCHMARK
; кроме того, не забывайте о возможностях эмуляции действий пользователя (щелчки мышью и нажатие клавиш), чтобы иметь возможность организовать тестирование виджетов.
В качестве вывода могу сказать, что TDD имеет свои преимущества. Но лично для меня недостатки все же весьма ощутимы, чтобы не применять его в своей работе постоянно. Конечно, иногда удобно таким способом написать и отладить какой-то сложный алгоритм. Но я считаю, что для более тривиальных случаев это уже избыточно. К тому же, код взаимодействия с оборудованием, сетью или БД писать через тестирование довольно накладно. Приходится задумываться о подставных объектах и прочих побочных элементах. Таким образом, модульные тесты полезны, но все должно быть в меру и по назначению. Главное - выбрать правильный момент и инструмент для реализации ваших задумок.
Текст в статье цветной из-за подсветки синтаксиса на самом сайте :) У меня в консоли он выводится в черно-белом виде.
А в гугл тесте текст цветной сразу.
В Qt Creator есть вкладка "Результаты тестирования", там упорядоченный вывод.
neyro_non
Хорошая статья. Давно интересовал вопрос: как заставить тест отображать разноцветный текст в консоли? У вас кажется именно это так и происходит?