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

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

Страниц: [1]   Вниз
  Печать  
Автор Тема: [Решено] Модуль QTest. Работа с исключениями.  (Прочитано 6118 раз)
8Observer8
Гость
« : Апрель 06, 2014, 16:43 »

Вместо QTest использую Google Test: http://www.prog.org.ru/topic_26944_0.html

Привет!

Я считаю, что я крайне небрежно написал следующий код. Так как мне приходиться править данные аж в трёх местах.

Сначала объясню ситуацию. У меня есть функция, которая принимает число из диапазона [5; 4*10^5] и это число должно заканчиваться на цифру 5. Функция возвращает аргумент в квадрате.

В случае, если не выполняется соглашение вызова, то функция выбрасывает исключения. Какие именно, это неважно. К проблеме это не относится.

Я написал костыли для отлавливания исключений в QTest. Но когда я хочу добавить данные для тестирования, то мне приходиться добавлять (править) в трёх местах:

1) Там, где я добавляю данные: входные данные и ожидаемые результаты:
Код
C++ (Qt)
void FiveAndFive::testCase1_data()
{
   QTest::addColumn<int>("number");
   QTest::addColumn<double>("expected");
 
   QTest::newRow("fiveAndFive_01") << 1 << 0.0;
   QTest::newRow("fiveAndFive_02") << 0 << 0.0;
   QTest::newRow("fiveAndFive_03") << 4 << 0.0;
   QTest::newRow("fiveAndFive_04") << 5 << 25.0;
   QTest::newRow("fiveAndFive_05") << 6 << 0.0;
   QTest::newRow("fiveAndFive_06") << 399995 << 159996000025.0;
   QTest::newRow("fiveAndFive_07") << 400000 << 160000000000.0;
   QTest::newRow("fiveAndFive_08") << 400001 << 0.0;
   QTest::newRow("fiveAndFive_09") << 400005 << 0.0;
}
 

2) Там, где я проверяю на исключения:
Код
C++ (Qt)
   try {
       double actual = fiveAndFive( number );
       bool result = qFuzzyCompare( actual, expected, delta );
       switch ( number ) {
       case 0:
       case 1:
       case 4:
       case 6:
       case 400001:
       case 400005:
           QVERIFY2( false, "Failure exception." );
           break;
       default:
           QString msg = QString("\nActual: %1"
                                 "\nExpected: %2"
                                 "\nDelta: %3").arg(actual).arg(expected).arg(delta);
           QVERIFY2( result, msg.toStdString().c_str() );
           break;
       }
 

3) И там, где отлавливаю исключение:
Код
C++ (Qt)
catch ( const LogicError& e ) {
       if ( number != 0 &&
            number != 1 &&
            number != 4 &&
            number != 6 &&
            number != 400001 &&
            number != 400005
            )
       {
           QVERIFY2( false, "Failure exception." );
       } else {
           QVERIFY2( true, "" );
       }
   } catch (...) {
       QVERIFY2( false, "Uncaught exception.");
   }
 

Я читал, что если возникают такие зависимости (что приходиться править в друх или более местах), то это означает, что код плохо написан. Я ума не приложу, как тут можно обойти правку в трёх местах.

Вот весь код.

Первый проект создавал, как Qt Unit Test

Код, который тестирует функцию:
tst_FiveAndFive.cpp
Код
C++ (Qt)
#include <QString>
#include <QtTest>
#include <stdexcept>
#include <sstream>
 
class LogicError : public std::logic_error {
public:
 
   LogicError( int argument ) : std::logic_error( "" ), m_argument( argument ) {
 
   }
 
   virtual const char *what( ) const throw () {
       return m_message.c_str( );
   }
 
   virtual ~LogicError( ) throw () {
 
   }
 
protected:
   int m_argument;
   std::string m_message;
};
 
class InvalidArgument : public LogicError {
public:
 
   InvalidArgument( int argument ) : LogicError( argument ) {
       std::stringstream stream;
       stream << argument;
       m_message = "Argument " + stream.str( ) + " mush be multiple of 5";
   }
};
 
class OutOfRange : public LogicError {
public:
 
   OutOfRange( int argument, int beginOfRange, int endOfRange ) : LogicError( argument ) {
       std::stringstream stream;
       std::string str_argument, str_beginOfRange, str_endOfRange;
 
       stream << argument;
       str_argument = stream.str();
       stream.str("");
 
       stream << beginOfRange;
       str_beginOfRange = stream.str();
       stream.str("");
 
       stream << endOfRange;
       str_endOfRange = stream.str();
       stream.str("");
 
       m_message = "Argument " + str_argument + " don't hit on the range [" +
               str_beginOfRange + ", " + str_endOfRange + "]";
   }
};
 
double fiveAndFive( int number ) throw (InvalidArgument, OutOfRange);
 
class FiveAndFive : public QObject
{
   Q_OBJECT
 
public:
   FiveAndFive();
 
   static inline bool qFuzzyCompare(double p1, double p2, double delta)
   {
       return (qAbs(p1 - p2) <= delta * qMin(qAbs(p1), qAbs(p2)));
   }
 
private Q_SLOTS:
   void testCase1_data();
   void testCase1();
};
 
FiveAndFive::FiveAndFive()
{
}
 
void FiveAndFive::testCase1_data()
{
   QTest::addColumn<int>("number");
   QTest::addColumn<double>("expected");
 
   QTest::newRow("fiveAndFive_01") << 1 << 0.0;
   QTest::newRow("fiveAndFive_02") << 0 << 0.0;
   QTest::newRow("fiveAndFive_03") << 4 << 0.0;
   QTest::newRow("fiveAndFive_04") << 5 << 25.0;
   QTest::newRow("fiveAndFive_05") << 6 << 0.0;
   QTest::newRow("fiveAndFive_06") << 399995 << 159996000025.0;
   QTest::newRow("fiveAndFive_07") << 400000 << 160000000000.0;
   QTest::newRow("fiveAndFive_08") << 400001 << 0.0;
   QTest::newRow("fiveAndFive_09") << 400005 << 0.0;
}
 
void FiveAndFive::testCase1()
{
   QFETCH( int, number );
   QFETCH( double, expected );
 
   double delta = 0.0001;
 
   try {
       double actual = fiveAndFive( number );
       bool result = qFuzzyCompare( actual, expected, delta );
       switch ( number ) {
       case 0:
       case 1:
       case 4:
       case 6:
       case 400001:
       case 400005:
           QVERIFY2( false, "Failure exception." );
           break;
       default:
           QString msg = QString("\nActual: %1"
                                 "\nExpected: %2"
                                 "\nDelta: %3").arg(actual).arg(expected).arg(delta);
           QVERIFY2( result, msg.toStdString().c_str() );
           break;
       }
   } catch ( const LogicError& e ) {
       if ( number != 0 &&
            number != 1 &&
            number != 4 &&
            number != 6 &&
            number != 400001 &&
            number != 400005
            )
       {
           QVERIFY2( false, "Failure exception." );
       } else {
           QVERIFY2( true, "" );
       }
   } catch (...) {
       QVERIFY2( false, "Uncaught exception.");
   }
}
 
QTEST_APPLESS_MAIN(FiveAndFive)
 
#include "tst_FiveAndFive.moc"
 

Файл проекта:
acmp_0003_five_and_five_tests.pro
Код
C++ (Qt)
#-------------------------------------------------
#
# Project created by QtCreator 2014-04-04T15:57:01
#
#-------------------------------------------------
 
QT       += testlib
 
QT       -= gui
 
TARGET = tst_FiveAndFive
CONFIG   += console
CONFIG   -= app_bundle
 
TEMPLATE = app
 
 
SOURCES += tst_FiveAndFive.cpp
SOURCES += ../acmp_0003_five_and_five/main.cpp
DEFINES += SRCDIR=\\\"$$PWD/\\\"

Второй проект создавал, как Empty Qt Project

Это файл, в котором находится тестируемая функция (для запуска тестов должна быть раскомментированна строка #define TESTING):
main.cpp
Код
C++ (Qt)
#include <stdexcept>
#include <string>
#include <sstream>
 
#define TESTING
#ifndef TESTING
 
#include <iostream>
#include <fstream>
 
class FileError : public std::runtime_error {
public:
 
   FileError( const std::string &fileIn ) : std::runtime_error( "" ), m_file( fileIn ) {
   }
 
   virtual const char* what( ) const throw() {
       return m_msg.c_str( );
   }
 
   std::string getFileName( ) const {
       return m_file;
   }
 
   virtual ~FileError() throw() {
 
   }
 
protected:
   std::string m_file;
   std::string m_msg;
};
 
class FileOpenError : public FileError {
public:
 
   FileOpenError( const std::string &fileNameIn ) : FileError( fileNameIn ) {
       m_msg = "Unable to open " + fileNameIn;
   }
};
 
class FileReadError : public FileError {
public:
 
   FileReadError( const std::string &fileNameIn, int lineNumIn ) :
       FileError( fileNameIn ), m_lineNum( lineNumIn ) {
       std::ostringstream ostr;
       ostr << "Error reading " << fileNameIn << " at line " << lineNumIn;
       m_msg = ostr.str( );
   }
 
   int getLineNum( ) const {
       return m_lineNum;
   }
 
protected:
   int m_lineNum;
};
 
class FileWriteError : public FileError {
public:
 
   FileWriteError( const std::string &fileNameIn ) : FileError( fileNameIn ) {
       m_msg = "Unable to write " + fileNameIn;
   }
};
#endif // TESTING
 
class LogicError : public std::logic_error {
public:
 
   LogicError( int argument ) : std::logic_error( "" ), m_argument( argument ) {
 
   }
 
   virtual const char *what( ) const throw () {
       return m_message.c_str( );
   }
 
   virtual ~LogicError( ) throw () {
 
   }
 
protected:
   int m_argument;
   std::string m_message;
};
 
class InvalidArgument : public LogicError {
public:
 
   InvalidArgument( int argument ) : LogicError( argument ) {
       std::stringstream stream;
       stream << argument;
       m_message = "Argument " + stream.str( ) + " mush be multiple of 5";
   }
};
 
std::string intToString( int number ) {
   std::stringstream stream;
   stream << number;
   return stream.str( );
}
 
class OutOfRange : public LogicError {
public:
 
   OutOfRange( int argument, int beginOfRange, int endOfRange ) : LogicError( argument ) {
       std::string str_argument, str_beginOfRange, str_endOfRange;
 
       str_argument = intToString( argument );
       str_beginOfRange = intToString( beginOfRange );
       str_endOfRange = intToString( endOfRange );
 
       m_message = "Argument " + str_argument + " don't hit on the range [" +
               str_beginOfRange + ", " + str_endOfRange + "]";
   }
};
 
#ifndef TESTING
void readData( const std::string &fileName, int &number )
throw (FileOpenError, FileReadError);
 
void writeResult( const std::string &fileName, int result )
throw (FileOpenError, FileWriteError);
 
double fiveAndFive( int number ) throw (InvalidArgument, OutOfRange);
 
int main() {
   std::string fileNameIn = "input.txt";
   int number = 0;
 
   try {
       readData( fileNameIn, number );
   } catch ( const FileError &e ) {
       std::cerr << e.what( ) << std::endl;
       return 1;
   } catch ( ... ) {
       std::cerr << "Uncaught exception." << std::endl;
       return 1;
   }
 
   int result = 0;
   try {
       result = fiveAndFive(number);
   } catch ( const LogicError &e ) {
       std::cerr << e.what( ) << std::endl;
       return 1;
   } catch ( ... ) {
       std::cerr << "Uncaught exception." << std::endl;
       return 1;
   }
 
   std::string fileNameOut = "output.txt";
   try {
       writeResult( fileNameOut, result );
   } catch ( const FileError &e ) {
       std::cerr << e.what( ) << std::endl;
       return 1;
   }
 
   return 0;
}
 
void readData( const std::string &fileName, int &number )
throw ( FileOpenError, FileReadError ) {
   std::ifstream file;
   file.open( fileName.c_str( ) );
   if ( file.fail( ) ) {
       throw FileOpenError( fileName );
   }
 
   int lineNumIn = 0;
   if ( !( file >> number ) ) {
       lineNumIn++;
       throw FileReadError( fileName, lineNumIn );
   }
}
 
void writeResult( const std::string &fileName, int result )
throw (FileOpenError, FileWriteError) {
   std::ofstream file;
   file.open( fileName.c_str( ) );
 
   if ( file.fail( ) ) {
       throw FileOpenError( fileName );
   }
 
   if ( !( file << result ) ) {
       throw FileWriteError( fileName );
   }
 
   file << std::endl;
}
#endif // TESTING
 
double fiveAndFive( int number ) throw (InvalidArgument, OutOfRange) {
   if ( (number % 5) != 0 ) {
       throw InvalidArgument( number );
   }
 
   const int beginOfRange = 5;
   const int endOfRange = 400000;
   if ( (number < beginOfRange) || (endOfRange < number) ) {
       throw OutOfRange( number, beginOfRange, endOfRange );
   }
 
   double result = (double) number * (double) number;
   return result;
}
 

Файл проекта:
acmp_0003_five_and_five.pro
Код
C++ (Qt)
SOURCES += \
   main.cpp
 

Заранее спасибо за ответы!
« Последнее редактирование: Октябрь 15, 2014, 07:00 от 8Observer8 » Записан
8Observer8
Гость
« Ответ #1 : Апрель 06, 2014, 18:30 »

Я на всякий случай, оставлю здесь заметку для тех кто использует C#. В среде Visual Studio Expess 2012 есть полная поддержка методологии Test Driven Development на уровне среды. Удобно создать решение, в котором и создавать тесты. Причём этот фреймворк поддерживает исключения и Mock-объекты. Среда поддерживает чистый TDD: пишем тесты и прямо из тестов генерируем классы и методы, которые будем тестировать (правой кнопкой по названию будущего класса, потом: Generate->Type Name, а для методов тестируемого класса: тоже правой кнопкой -> Generate -> Method Stub). Подробная пошаговая инструкция здесь: http://msdn.microsoft.com/en-us/library/hh212233.aspx
« Последнее редактирование: Апрель 06, 2014, 20:51 от 8Observer8 » Записан
Fat-Zer
Гость
« Ответ #2 : Апрель 06, 2014, 21:30 »

QTest::addColumn<bool>("expect_exception");
Записан
8Observer8
Гость
« Ответ #3 : Апрель 07, 2014, 06:53 »

QTest::addColumn<bool>("expect_exception");

Спасибо огромное! Исчезла проблема правки данных в трёх местах Улыбающийся

Если в коде есть какие-то недочёты, то просьба сообщить:
Код
C++ (Qt)
void FiveAndFive::testCase1_data()
{
   QTest::addColumn<int>( "number" );
   QTest::addColumn<double>( "expected" );
   QTest::addColumn<bool>( "isExpectedException" );
 
   bool exception = true;
   bool no_exception = false;
 
   QTest::newRow("fiveAndFive_02") << 0 << 0.0 << exception;
   QTest::newRow("fiveAndFive_01") << 1 << 0.0 << exception;
   QTest::newRow("fiveAndFive_03") << 4 << 0.0 << exception;
   QTest::newRow("fiveAndFive_04") << 5 << 25.0 << no_exception;
   QTest::newRow("fiveAndFive_05") << 6 << 0.0 << exception;
   QTest::newRow("fiveAndFive_06") << 399995 << 159996000025.0 << no_exception;
   QTest::newRow("fiveAndFive_07") << 400000 << 160000000000.0 << no_exception;
   QTest::newRow("fiveAndFive_08") << 400001 << 0.0 << exception;
   QTest::newRow("fiveAndFive_09") << 400005 << 0.0 << exception;
}
 
void FiveAndFive::testCase1()
{
   QFETCH( int, number );
   QFETCH( double, expected );
   QFETCH( bool, isExpectedException );
 
   double delta = 0.0001;
 
   try {
       double actual = fiveAndFive( number );
       bool result = qFuzzyCompare( actual, expected, delta );
       if ( isExpectedException ) {
           QVERIFY2( false, "There is no exception." );
       } else {
           QString msg = QString("\nActual: %1"
                                 "\nExpected: %2"
                                 "\nDelta: %3").arg(actual).arg(expected).arg(delta);
           QVERIFY2( result, msg.toStdString().c_str() );
       }
 
   } catch ( const LogicError& e ) {
       if ( !isExpectedException ) {
           QVERIFY2( false, "Exception was occur." );
       } else {
           QVERIFY2( true, "");
       }
   } catch (...) {
       QVERIFY2( false, "Uncaught exception.");
   }
}
 
« Последнее редактирование: Апрель 07, 2014, 06:55 от 8Observer8 » Записан
8Observer8
Гость
« Ответ #4 : Апрель 07, 2014, 11:55 »

Я оформил хороший пример по этой теме. См. первое сообщение по следующей ссылке; пример называется "Пример разработки ПО через тестирование" (это в конце инструкции): http://www.prog.org.ru/topic_26413_0.html
« Последнее редактирование: Апрель 07, 2014, 11:59 от 8Observer8 » Записан
8Observer8
Гость
« Ответ #5 : Апрель 30, 2014, 06:30 »

Решил проблему. Теперь использую GTest: http://www.prog.org.ru/topic_26944_0.html

При нормальных условиях функция не должна выбрасывать исключения, поэтому всегда в тестах надо использовать ASSERT_NO_THROW:
Код
C++ (Qt)
TEST( test001, normalTest ) {
   int input = 15;
   int expected = 120;
 
   int actual = 0;
   ASSERT_NO_THROW({
                       actual = myFunc( input );
                   });
   ASSERT_EQ( expected, actual );
}
 

Если функция при определённых входных данных выбрасывает исключение и вы его ожидаете (например, ваше исключение называется OutOfRange), то надо указать тип исключения:
Код
C++ (Qt)
TEST( test002, exeptionTest ) {
   int input = 10001;
 
   ASSERT_THROW({
                    myFunc( input );
   }, OutOfRange);
}
 

Если вы хотите сравнивать вещественные: ожидаемый результат и выходной результат функции с погрешностью дельта, то надо использовать ASSERT_NEAR:
Код
C++ (Qt)
TEST( test003, normalTest ) {
   int input = 5;
   double expected = 25.0;
 
   double actual = 0.0;
   ASSERT_NO_THROW({
                       actual = myFunc( n );
                   });
   double delta = 0.001;
   ASSERT_NEAR( expected, actual, delta );
}
 
Записан
8Observer8
Гость
« Ответ #6 : Июнь 26, 2014, 09:06 »

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

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


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