Завдання на лабораторну роботу

Мета роботи

Вивчити можливості спеціального модулю QtTestLib та реалізувати тести із застосуванням мови С++ та Qt.

5.2 Основні теоретичні відомості

Unit testing – iзольована перевiрка кожного окремого елементу (модуль, функцiя, клас) шляхом запуску тестiв в штучному середовищi. Цiль: виявлення локалiзованих в модулi помилок, а також визначення ступеня готовностi системи до переходу на iнший рiвень розробки i тестування. Основна iдея: оцiнюючи кожен елемент iзольовано i пiдтверджуючи коректнiсть його роботи, точно встановити помилку значно простiше, нiж якби елемент був частиною системи.

Для створення тестів Qt надає спеціальний модуль QtTestLib, який розроблений для того, щоб спростити тестування класів програми. Також він включає в себе можливості проведення тестів класів графічного інтерфейсу і дозволяє "симулювати" клавіатуру і мишу.

5.2.1 Створення тестів в QT

Тести корисно створювати до початку реалізації коду. Це дозволить вам при написанні тесту краще осмислити і зрозуміти завдання, поставивши собі запитання – що потрібно зробити для додавання реалізації.

Для демонстрації візьмемо простий приклад: припустимо, потрібно реалізувати клас з методами для знаходження максимуму і мінімуму двох чисел. Перше завдання полягає в підготовці тестових даних, які будуть виступати в якості зразків для тестування. Візьмемо для цієї мети чотири пари чисел: (25, 0), (-12, -15), (2007, 2007) і (-12, 5). Тепер, коли тестові дані готові, можна починати писати тести. Існують угоди для назви тестуючого класу та його методів, які встигли закріпитися і зарекомендувати себе на практиці з найкращого боку. А саме:

- називайте тестуючий клас ім'ям тестованого з префіксом Test. Наприклад: якщо ми тестуємо клас MyClass, то тестуючий клас буде називатися Test_MyClass;

- називайте тестові слоти (методи) іменами тестованих методів.

 

#include <QtTest>

#include "MyClass.h"

 

class Test_MyClass : public QObject {

Q_OBJECT

private slots:

void min();

void max();

};

 

 

void Test_MyClass::min()

{

MyClass myClass;

QCOMPARE(myClass.min(25, 0), 0);

QCOMPARE(myClass.min(-12, -5), -12);

QCOMPARE(myClass.min(2007, 2007), 2007);

QCOMPARE(myClass.min(-12, 5), -12);

}

 

void Test_MyClass::max()

{

MyClass myClass;

QCOMPARE(myClass.max(25, 0), 25);

QCOMPARE(myClass.max(-12, -5), -5);

QCOMPARE(myClass.max(2007, 2007), 2007);

QCOMPARE(myClass.max(-12, 5), 5);

}

 

QTEST_MAIN(Test_MyClass)

#include "test.moc"

 

У лістингу показана програма, яка повинна буде проводити тест методів min() і max() класу MyClass. У тестовій програмі необхідно включити заголовки QTest. Тестовий клас повинен бути успадкований від класу QObject і, для створення спеціальної метаінформації, містити в своєму визначенні макрос QObject. Це дозволить викликати слоти класу при виконанні, включаючи його тестові слоти в секції private.

Макрос QCOMPARE() приймає два аргументи – отриманий і очікуваний результат, а потім порівнює їх. Якщо значення не збігаються, то тоді виконання тестового методу переривається повідомленням про не пройдений тест.

Нам потрібна функція main, в якій буде виконуватися кожен тест. З огляду на те, що для проведення тестів ця функція виглядає однаково, Qt надає для її заміни макрос QTEST_MAIN().

На завершення ми повинні включити метаінформацію, що згенерувала МОС.

Після створення тесту можна приступити до реалізації методів тестованого класу.

 

#ifndef MyClass_h_

#define MyClass_h_

 

class MyClass {

public:

int min(int n1, int n2)

{

return n1 < n2 ? n1 : n2;

}

 

int max(int n1, int n2)

{

return n1 > n2 ? n1 : n2;

}

};

#endif //MyClass_h_

 

Клас MyClass реалізує два методи для знаходження мінімуму і максимуму.

 

SOURCES = test.cpp

HEADERS = MyClass.h

CONFIG += qtestlib

win32:TARGET = ../TestLib

 

У pro-файлі, в секції config повинна бути додана опція qttest. Ім'я заголовочного файлу тестованого класу зазначено для того, щоб за будь-яких його змінах можна було скомпілювати тест заново. При першому проведенні тесту корисно почати з перевірки на відмову, тобто нам потрібно модифікувати перевіряється метод так, щоб тест завершувався невдачею. Це допоможе нам переконатися в тому, що тест справді виконується і перевіряє те, що потрібно. Для цього поміняйте в методах min() і max() класу MyClass знаки порівняння на протилежні. Тепер откомпіліруем і запустимо тест. На екрані з'явиться наступне:

 

********* Start testing of Test_MyClass *********

Config: Using QTest library 4.7.4, Qt 4.7.4

PASS : Test_MyClass::initTestCase()

FAIL! : Test_MyClass::min() Compared values are not the same

Actual (myClass.min(25, 0)): 25

Expected (0): 0

Loc: [../TestLib/test.cpp(24)]

FAIL! : Test_MyClass::max() Compared values are not the same

Actual (myClass.max(25, 0)): 0

Expected (25): 25

Loc: [../TestLib/test.cpp(34)]

PASS : Test_MyClass::cleanupTestCase()

Totals: 2 passed, 2 failed, 0 skipped

********* Finished testing of Test_MyClass *********

 

Методи initTestCase() і cleanupTest() викликаються на початку і кінці тесту відповідно. Ці методи не трактуються як тест-методи. Вони виконуються при запуску тестів і служать для ініціалізації та очищення тесту. Крім того, на екрані ми бачимо інформацію про те, що тест пройшов невдало: повідомлення "FAIL!", імена тестів "Test_MyClass::min()" і "Test_MyClass::max()", актуальні значення (Actual) і очікувані (Expected). Наш тест завершився невдачею, а це означає, що перевірка роботи тесту вдалася - він дійсно здатний відстежувати помилки. Тепер поміняємо оператори порівняння в класі MyClass так, як це показано в лістингу. Скомпіліруем і запустимо тест ще раз. Ми побачимо на екрані такі повідомлення:

 

********* Start testing of Test_MyClass *********

Config: Using QTest library 4.7.4, Qt 4.7.4

PASS : Test_MyClass::initTestCase()

PASS : Test_MyClass::min()

PASS : Test_MyClass::max()

PASS : Test_MyClass::cleanupTestCase()

Totals: 4 passed, 0 failed, 0 skipped

********* Finished testing of Test_MyClass *********

 

Що говорить про те, що всі наші тести пройшли вдало.

 

Тести з передачею даних

До теперішнього моменту ми вписували дані для проведення тесту в макрос QCOMPARE(). Подібний підхід викликає небажаний ефект дублювання коду. Для мінімізації дублювання коду QtTestLib надає можливість проведення тестів з передачею даних. Такий підхід дозволяє відокремити тестовий код від даних, помістивши їх в окреме місце. Цим місцем є слот, який надається для кожного тестуючого слота. Цей слот повинен називатися так само, як і тестуючий, але з постфіксом _data.

 

class Test_MyClass : public QObject {

Q_OBJECT

private slots:

void min_data();

void max_data();

 

void min();

void max();

};

 

У тестуючий клас ми ввели два додаткових слота: min_data() і max_data(), які будуть забезпечувати даними слоти для тестування.

 

void Test_MyClass::min_data()

{

QTest::addColumn<int>("arg1");

QTest::addColumn<int>("arg2");

QTest::addColumn<int>("result");

 

QTest::newRow("min_test1") << 25 << 0 << 0;

QTest::newRow("min_test2") << -12 << -5 << -12;

QTest::newRow("min_test3") << 2007 << 2007 << 2007;

QTest::newRow("min_test4") << -12 << 5 << -12;

}

 

У слоті min_data() необхідно створити таблицю тестових даних. для завдання її стовпців використовується метод QTest: addColumn(). Після цього ми додаємо деякі дані в цю таблицю за допомогою методу QTest :: newRow(). Рядок, переданий в цей метод, є ідентифікатором рядка таблиці. Кожен виклик створить свій власний рядок, а за допомогою оператора « в нього додадуться дані: два параметра і очікуваний результат застосування методу min().

 

void Test_MyClass::max_data()

{

QTest::addColumn<int>("arg1");

QTest::addColumn<int>("arg2");

QTest::addColumn<int>("result");

 

QTest::newRow("max_test1") << 25 << 0 << 25;

QTest::newRow("max_test2") << -12 << -5 << -5;

QTest::newRow("max_test3") << 2007 << 2007 << 2007;

QTest::newRow("max_test4") << -12 << 5 << 5;

}

 

Реалізація слота даних max_data() аналогічна слоту min_data(). За винятком інших ідентифікаторів рядків таблиці і деяких відмінностей в очікуваних результатах.

 

void Test_MyClass::min()

{

MyClass myClass;

QFETCH(int, arg1);

QFETCH(int, arg2);

QFETCH(int, result);

 

QCOMPARE(myClass.min(arg1, arg2), result);

}

 

Метод даних задає таблицю з чотирьох рядків, тому слот min() тепер буде запускатися чотири рази - для кожного рядка таблиці даних. Ми використовуємо три макроси QFETCH() для створення локальних змінних arg1, arg2, result і внесення в них даних. Зауважте, ім'я має збігатися з ім'ям елемента тестових даних, а якщо елемент даних з цим ім'ям буде не знайдений, то тест завершиться з повідомленням про помилку. Тепер для проведення тестів нам потрібен тільки один макрос QCOMPARE(). Такий підхід дозволяє легко додавати нові дані для тесту без модифікації самого тесту.

 

void Test_MyClass::max()

{

MyClass myClass;

QFETCH(int, arg1);

QFETCH(int, arg2);

QFETCH(int, result);

 

QCOMPARE(myClass.max(arg1, arg2), result);

}

 

Реалізація тестового слота max() аналогічна реалізації слота min() з тією різницею, що для проведення тесту викликається метод MyClass :: max() замість методу MyClass :: min().

 

На завершення, ми повинні надати функцію main() за допомогою макросу QTESTMAIN() і включити метадані, згенеровані МОС:

 

QTEST_MAIN(Test_MyClass)

#include "test.moc"

 

5.2.3 Створення тестів графічного інтерфейсу

QTestLib надає механізм для тестування графічного інтерфейсу. Припустимо, що ми хочемо протестувати поведінку віджета однострочного текстового поля QLineEdit. Перш за все нам потрібно створити клас, який містить тестовий метод.

У реалізації тестового методу edit() ми створюємо віджет QLineEdit. Потім ми імітуємо введення "ABCDEFGH", використовуючи метод QTest :: keyClicks(), який імітує натискання на серію клавіш клавіатури. У необов'язкових параметрах цього методу можна передавати модифікатори клавіатури, а також затримку, в мілісекундах, після кожного натискання на клавішу. Клас QTest надає методи, що дозволяють симулювати події не тільки клавіатури, але і миші.

 

#include <QtTest>

#include <QtGui>

 

//

class Test_QLineEdit : public QObject {

Q_OBJECT

private slots:

void edit();

};

 

 

void Test_QLineEdit::edit()

{

QLineEdit txt;

QTest::keyClicks(&txt, "ABCDEFGH");

 

QCOMPARE(txt.text(), QString("ABCDEFGH"));

QVERIFY(txt.isModified());

}

 

QTEST_MAIN(Test_QLineEdit)

#include "test.moc"

 

Ми використовуємо макрос QCOMPARE() для того, щоб перевірити на збіг текст однострочного текстового поля і очікуваний текст. Після того як текст в віджеті поля був змінений, виклик методу isModified() повинен повернути значення true. Ми перевіряємо це за допомогою макросу QVERIFY(). Цей макрос оцінює передане вираз, і якщо воно істинне, то виконання тесту триває. В іншому випадку проводиться відображення повідомлення про помилку і виконання тесту припиняється.

Для створення тесту потрібен клас, який буде містити тестові слоти. Цей клас повинен бути успадкований від класу QObject. Тестова програма повинна містити макрос QTESTMAIN(), який замінює функцію main() для запуску всіх тестових методів.

Макрос QCOMPARE() порівнює результуючі значення з очікуваними. Якщо значення ідентичні, то виконання тесту буде продовжено, якщо ні - тест буде зупинений з відображенням повідомлення про помилку.

Макрос QVERIFY() перевіряє правильність умови. Якщо значення дорівнює true, то виконання тесту триває. Якщо немає, то тест далі не виконується і виконується відображення повідомлення про помилку.

Щоб уникнути проблем, пов'язаних з повторенням коду, QtTestLib надає можливість створення тестів з передачею даних. Все що потрібно - це просто додати ще один слот в private секцію нашого класу. Слот для тестових даних повинен називатися так само, як і тестовий слот, але з постфіксом _data. Тестові дані мають формат звичайної таблиці. Призначення макросу QFETCH() полягає в створенні локальних змінних і заповненні їх даними.

QtTestLib надає можливість для тестування графічного інтерфейсу.

Завдання на лабораторну роботу

1.3.1 Ознайомитися з теоретичними відомостями щодо тестування за допомогою QtTestLib.

1.3.2 Реализувати на мові C++ з використанням QtTestLib набір тестів для програми, реалізованої у під час написання курсового проекту з об'єктно-орієнтованого програмування (тестування методів класу та одного з елементів графічного інтерфейсу).

1.3.3 Виконати аналіз отриманих результатів.

1.3.4 Оформити звіт та відповісти на контрольні питання.

1.4 Зміст звіту

1.4.1 Тема та мета роботи.

1.4.2 Код тестів, реалізованих на мові С++.

1.4.3 Аналіз отриманих результатів тестування.

1.4.5. Висновки, що містять відображують результати виконання роботи та їх критичний аналіз.

 


 

Література

 

1. Bevan N. Specifying and Measuring Quality in Use // Proceedings of the 22nd international conference on Software engineering, 2000. – P. 322–331.

2. Halpern M. Addressing Quality Risk in Collaborative Automotive Design: Commentary, – Gartner Group, 2001. – P. 262–293.

3. Hassenzahl M., Platz A., Burmester M., Lehner K. Hedonic and Ergonomic Quality Aspects Determine a Software's Appeal // Proceedings of the CHI 2000 conference on Human factors in computing systems, 2000. – P. 193–201.

4. Hutcheson M. L. Software Testing Fundamentals: Methods and Metrics. John Wiley & Sons. – 2003. – 408 p.

5. IEEE Standard for a Software Quality Metrics Methodology Copyright ©, Institute of Electrical and Electronics Engineers, Inc. 2005.

6. Kan H.N. Metrics and Models in Software Quality Engineering, Second Edition, 2002. – 560 p.

7. Pandian C. R. Software Metrics: A Guide to Planning, Analysis, and Application Auerbach Publications, 2004. – 312 p.

8. Хорстман К., Корнелл Г. Java 2. Библиотека профессионала, том 1. Основы, 7-е издание. – М: Издательский дом "Вильямс", 2006. – 896с.

9. Хорстман К., Корнелл Г. Java 2. Библиотека профессионала, том 2. Тонкости программирования, 7-е издание. – М: Издательский дом "Вильямс", 2006. – 1168с.