QTestSuite

Żeby nie było jakiś nieporozumień, nie przepadam za Qt, pewnie wszyscy odwiedzjący mnie czytelnicy już się domyślili, albo i nie, że preferuję wx-y. Tak wolę wxWidgets, nie lubię Qt-a. To nie znaczy, że w wielu komercyjnych projektach nie korzystałem z tego frameworka, ale prywatnie raczej nigdy nie będę. Od dawno już utożsamiam się z indoktrynacją kierunku, że biblioteki tego typu powinny tylko udostępniać narzędzia do budowania GUI, bo przecież obecnie cala reszta dostępna jest w STL lub booscie, a jak nie to w masie innych bibliotek. Tak, czy siak, nie miejsce tutaj na wynurzenia i podsycanie ewentualnym flejmów.

Ostatnio miałem trochę styczności z narzędziem do tworzenia testów w tym toolkicie. QTestLib jest w miarę prostym narzędziem, nie uświadczymy w nim niektórych elementów i możliwości jakie oferuje sztandarowy CppUnit czy boost:test. Ale za to ma również fajne funkcjonalności. Jedną z nich jest możliwość przeprowadzania testów w pragmatyce data-driven testing, czyli pozwala przeprowadzać wiele tych samych testów na danym case-ie z zastosowaniem różnych zbiorów danych. Innym, chociaż nie używanym przeze mnie może być Benchmarking. Pewnie coś w działaniu zblizonego lub podobnego do mojego prostego TestBenchmarka.

Niestety nie znajdziemy tutaj, żadnych narzędzi pomocnych przy pisaniu nieco bardziej rozbudowanych testów i scenariuszy. Autorom QTestLib-a przyświecała prosta idea - jeden exec = jeden test. Ma to swoje wady, o których tutaj piszę, ale również zalety. Jeśli coś pójdzie nie tak z jednym testem, to nie wpłynie to na pozostałe. Ale tak naprawdę, to wszystko bardzo zależy od organizacji samych testów i testowania.

Zatem wiemy już, że brakuje możliwości tworzenia wielu test case-ów i test suite-ów oraz dowolnego ich organizowania, jak to możliwe jest w wspomnianej już konkurencji. Nie pozostało mi nic innego jak spróbować ten fakt naprawić.

Bez żadnych problemów można uruchomić wiele testów w jednej aplikacji. Wystarczy zdefiniować własny entry point, czyli funkcję main i w niej korzystać z QTest::exec, zamiast wykorzystania makr heleperów - QTEST_MAIN lub QTEST_APPLESS_MAIN, robiący de facto to samo. Czyli coś w stylu:

int main(int argc, char* argv[]) {
	int ret = 0;
 
	Test1 t1;
	ret |= QTest::qExec(&t1, argc, argv);
 
	Test2 t2;
	ret |= QTest::qExec(&t2, argc, argv);
 
	return ret;
}

Takie rozwiązania i zalecenia można znaleźć w sieci i społeczności qtowców. W wielu przypadkach są one wystarczające.
No, ale ja chciałem osiągnąć coś więcej, budowanie hierarchii testów, co udało mi się dokonać w prosty sposób.

Mój wynalazek - QTestSuite - dostarcza odpowiednie do tego narzędzia: klasę TestSuite do grupowania testów innych grup testów oraz TestRunner do uruchamiania całego drzewa, pojedynczych grup (suite) lub testów (case).

Chcąc zbudować proste drzewo testów w postaci:

|--- master
  |--- myTestSuite
    |---TestCase1
    |---TestCase2

Wystarczy odpowiednio zorganizować testy w funkcji main:

int main(int argc, char* argv[]) {
 
	TestCase1 t1;
	TestCase2 t2;
 
	TestSuite suite("myTestSuite");
	suite.add(&t1);
	suite.add(&t2);
 
	TestRunner::master()->add(&suite);
 
	return TestRunner::run(argc, argv);
}

QTestSuite dostarcza także kilka prostych makr, jak QTEST_SUITE_MAIN i QTEST_SUITE_APPLESS_MAIN, których działanie jest adekwatne do tych standardowych z QTestLib.

Prócz tego, na wzór boost::test i cppunit, możliwe jest automatyczne rejestrowanie testów, co może być praktyczne przy definiowaniu prostych testów w jedną grupę, tą najwyzszego poziomu - master.

Wykorzystanie tego mechanizmu w poniższy sposób:

Class TestCase1 { ... }
QTEST_SUITE_REGISTRATION(TestCase1);
 
Class TestCase2 { ... }
QTEST_SUITE_REGISTRATION(TestCase2);
 
QTEST_SUITE_MAIN();

Jest w działaniu odpowiednikiem takiej definicji:

int main(int argc, char* argv[]) {
 
	TestCase1 t1;
	TestRunner::master()->add(&t1);
 
	TestCase2 t2;
	TestRunner::master()->add(&t2);
 
	return TestRunner::run(argc, argv);
}

Wynikiem działania powyższego kodu (dołączony do źródeł), będzie następujący rezultat:

C:\Users\malcom\qt_test\example>test.exe
********* Start testing of TestCase1 *********
Config: Using QTest library 4.8.5, Qt 4.8.5
PASS   : TestCase1::initTestCase()
FAIL!  : TestCase1::test1() '1 + 2 == 2' returned FALSE. ()
.\main.cpp(9) : failure location
PASS   : TestCase1::test2()
PASS   : TestCase1::cleanupTestCase()
Totals: 3 passed, 1 failed, 0 skipped
********* Finished testing of TestCase1 *********
********* Start testing of TestCase2 *********
Config: Using QTest library 4.8.5, Qt 4.8.5
PASS   : TestCase2::initTestCase()
PASS   : TestCase2::test1()
PASS   : TestCase2::test2()
PASS   : TestCase2::cleanupTestCase()
Totals: 4 passed, 0 failed, 0 skipped
********* Finished testing of TestCase2 *********

Niestety nie udało mi się w prosty sposób zmusić, aby zamiast nazw test-caseów wyświetlany był również namespace, czyli ścieżka od mastera, poprzez poszczególne suity, aż do konkretnego testu.

Jedne rozwiązanie, jakie wpadło mi do głowy, aby zmieniać przed wywołaniem nazwę klasy zaszyta w meta-informacjach, wymagałoby ingerowania w obiekty zawierające te dane. Dane te głównie generowane są przez preprocesor moc do postaci stałych statycznych. Przez co takie rozwiązanie byłoby silnie implementation definied i mógłby kiedyś porostu przestać działać, gdyby zmieniły się jakieś bebechy Qt. Także, zrezygnowałem z tego.

Niemniej, nie udało mi się znaleźć żadnego innego prostego rozwiązania tego problemu. Publiczny interfejs QTesta jest bardzo prosty, a cala logika i mechanika leży w implementacji tej biblioteki.

Źródła można znaleźć na githubie jako gist:7071779. Dołączono tam również dwa przykłady, jakie przedstawiono wyżej, jedno oparte na makrach, drugie na ręcznie definiowanej funkcji main i rejestracji testów.

W sieci znalazłem kilka innych modyfikacji QTtestLib-a poprawiających korzystanie z tej biblioteki, a głównie pozwalających uruchamiać kilka testów w jednym programie. Szczegóły u źródeł: Running Multiple Unit Tests oraz Improving QtTest usability with QtTestUtil.

Added 18-11-2013 @ 23:20

Trafiłem jeszcze w sieci na inny materiał związany z QTestami i poprawa produktywności w korzystaniu z tej biblioteki. W wpisie Increase your QTest productivity autor poruszył podobne problemy, jakie przedstawiłem w niniejszej notce, zaproponował również ciekawe rozwiązania.

2 przemyślenia nt. „QTestSuite”

  1. „Od dawno już utożsamiam się z indoktrynacją kierunku, że biblioteki tego typu powinny tylko udostępniać narzędzia do budowania GUI, bo przecież obecnie cala reszta dostępna jest w STL lub booscie, a jak nie to w masie innych bibliotek. Tak, czy siak, nie miejsce tutaj na wynurzenia i podsycanie ewentualnym flejmów.”

    Rozumiem Twój punkt widzenia, jednak musisz zdać sobie sprawę z kilku spraw:
    a) Qt rozwinęło się do obecnej formy za pomocą KDE oraz QTopii. W tej ostatniej grzebała Nokia oraz kilka innych firm. Ponieważ QTopia witała na urządzeniach mobilnych a KDE dostarczał manager pulpitu dla Linuxa, gdy BOOST jeszcze nie istniał formalnie jako projekt. Właśnie wtedy powstała znacząca część bibliotek Qt, jak np. qt-xml, qt-multimedia itd. Z powodu braków w dostępnych bibliotekach programiści doprogramowali potrzebne im narzędzia.

    b) Kwestie licencyjne – nie każdy lubi rozpowszechniać kod do swoich projektów, zatem GPL nie miał tu zastosowania. Qt zostało więc obudowane zestawem bibliotek, które licencyjnie były zgodnie z oprogramowaniem o zamkniętym kodzie źródłowym.

    c) Spójność – programiści Qt błogosławią „pełność” biblioteki. Za pomocą jednego toolkitu mogą oprogramować całość i portować aplikacje na wiele platform, bez potrzeby instalacji wszystkich ustrojstw i zapewnienia „zależności”. Wiem – sam portowałem na Windows z Linuxa. Wystarczy załączyć biblioteki dostępne z SDK Qt i projekt śmiga.

    d) Ciągle, jeśli masz ochotę, możesz używać boosta i dowolnych innych bibliotek – wszystko zależy od definicji procesu kompilacji i flag kompilatora, szczególnie że Qt można budować za pomocą Visual Studio oraz mingw (windows), ICC oraz GCC (Linux), GCC (OSX).

    Dodatkowo wiesz, że interpretacja od zera sockets na różnych platformach wygląda w odmienny sposób. W przypadku Qt (qt-network) niezależnie od platformy jest to zunifikowane a kod jest przenośny bez potrzeby zapewnienia niczego więcej poza Qt SDK.

  2. A mialo nie byc flejmow i prowokacji ;)

    Mati, ja to wszystko wiem, i podobnie moge powiedziec o wx i wielu innych bibliotekach… a w wiekszosci nich brakuje niestety prawdziwego modern C++…

    Z socketami, akurat na desktopach nie jest zle. Sam cos wiele razy skrobalem cross-platformowego. No i w C++14 prawdopobnie beda sockety, tak jak dzieki C++11 mamy watki i mase innych fajnych rzeczy ;)

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *