📜 ⬆️ ⬇️

Asynchronous unit testing for C ++

Nowadays, the developer is already extremely difficult to bypass the asynchronous interaction between blocks of code. This includes working with web services, working with browser engines, and working with threads, etc. etc. can be listed indefinitely. And as a consequence, there is the problem of organizing unit testing of objects that perform operations asynchronously, or rather, in a tool that implements the ability to create asynchronous tests. The “asynchronous test” is understood as such a test that we can complete ourselves from any thread at any time, from any function.

As an introduction, I would like to note that I personally happened to see only one implementation of the asynchronous testing library, and then only for Silverlight 3 and 4 (from the Silverlight Toolkit package).

[TestClass] public class MyTest_test : SilverlightTest { [TestMethod] [Asynchronous]//    public void test_AsyncExample() { //      Thread cThread = new Thread( delegate() { //-   //   ! <b>EnqueueTestComplete();</b> } ); cThread.Start(); } } 

Of course, I didn’t do a large-scale search, but a cursory inspection showed that there was nothing like that except for Silvrlight, unfortunately, not for the .net Framework, nor for native C ++. And all sorts of clever solutions with locks, runs in background threads do not count. This only complicates and delays the testing process.

And so the tool offered to your attention is called QuickUnit , this is an open library written in C ++ using QT 4.7 distributed under the LGPL 2.1 license. Why it is implemented in QT, but because its implementation of the metadata system is perfectly suited for solving the problem of asynchronous testing (it reminds me very much of the reflection in .net). In the future, implementation is planned for the .net and mono platforms.
')
Before we start examining the library itself, let's consider a small example: let's imagine that we need to write a unit test for a class that performs operations in a background thread. For example, we will write a small test for the QWebView :: load (...) method; since we will get the result of executing this function only by processing the signal loadFinished (bool), For comparison, we first write an “asynchronous” test using CPPUNIT, and then using QuickUnit and compare the result.

Consider a test class implemented using CPPUNIT:


 #ifndef CWEBVIEWTEST_H #define CWEBVIEWTEST_H class CWebViewTest : public QObject, public CPPUNIT_NS::TestFixture { Q_OBJECT public: CWebViewTest( QObject *parent = 0 ); ~CWebViewTest(); CPPUNIT_TEST_SUITE( CWebViewTest ); CPPUNIT_TEST( test_WebView_Load ); CPPUNIT_TEST_SUITE_END(); public: //  virtual void setUp() { m_bPassed = false; m_pView = new QWebView( 0 ); }; //     virtual void tearDown() { delete m_pView; }; private: //  QWebView * m_pView; //    bool m_bPassed; private: //   void test_WebView_Load() { connect( m_pView, SIGNAL( loadFinished( bool ) ), SLOT( slot_load_finished( bool ) ) ); m_pView->load( QUrl("http://www.google.ru") ); /*      ,         loadFinished( bool ) */ //    (    ) while ( !m_bPassed ) { QApplication::processEvents( QEventLoop::ExcludeUserInputEvents ); } } private slots: void slot_load_finished( bool bOk ) { CPPUNIT_ASSERT(bOk); CPPUNIT_ASSERT( m_pView->page()->totalBytes() > 100 ); m_bPassed = true;//   } }; #endif // CWEBVIEWTEST_H 

The main problem is to keep the executing thread in the test function until the loadFinished (bool) signal in the slot_load_finished slot (bool bOk) has been fully processed, and only then the flow can be started up. If you do not keep the flow, then in fact the test can not be considered correct, since it is more likely to end prematurely. The easiest way to keep the executing thread is to force the processing of messages in the queue:

 //    (    ) while ( !m_bPassed ) { QApplication::processEvents( QEventLoop::ExcludeUserInputEvents ); } 

However, this method is laborious for the processor, of course it is not fatal, but does not make it clear at the testing and development stage how the code you implement actually loads the processor.

Now consider the same task, but using QuickUnit:


 #ifndef ASYNCHRONOUS_TEST_EXAMPLE_H #define ASYNCHRONOUS_TEST_EXAMPLE_H #include <QS_QuickUnit.h> #include <QtWebKit\QtWebKit> class AsynchronousTestExample : public QS_TestClass { Q_OBJECT private: QWebView * m_pView; private slots: //  void test_LoadWebPage() { QS_BEGIN_TEST; m_pView = new QWebView( 0 ); connect( m_pView, SIGNAL( loadFinished( bool ) ), SLOT( slot_loadFinished( bool ) ) ); m_pView->load( QUrl( "http://custom.site.com" ) ); QS_END_TEST; }//      ,     ! //  (    , //      test_) void slot_loadFinished ( bool ok ) { QS_BEGIN_TEST; //  QS_IS_TRUE( ok ); QS_IS_TRUE( m_pView->page()->totalBytes() > 100 ); QS_END_TEST; QS_TEST_COMPLETE;//,     } }; #endif //ASYNCHRONOUS_TEST_EXAMPLE_H 

As you can see from the example, the declarative part of the test class is minimal. The problem of retention has disappeared, the code has become smaller and clearer. But as it always happens for convenience you have to pay:
  1. If the developer forgets to indicate that the test should be completed, the system will wait forever (we have plans for a timeout for such cases, which would not slow down the testing process as a whole) the completion team;
  2. This library is implemented on QT, i.e. Before using QuickUnit, you must first install QT, and if you are using Visual Studio then the add-on for the studio.

Let's sum up:

what we get if we use QuickUnit:
  1. There is no need to wrestle with how to keep the flow in the test function;
  2. The minimum declarative part of the test class;
  3. Flexibility in building tests, for complex testing a lot of continuous blocks of code;
  4. The small size of the library;
  5. Cross-platform.

Disadvantages:
  1. Binding to QT;
  2. Carefully write tests, do not forget about the completion.

A brief look at the basic requirements and capabilities of QuickUnit.

The test class itself must be a QS_TestClass descendant and a QT class (the Q_OBJECT macro is declared). To separate the test slots from outsiders, the first have the prefix test_. All test slots are executed in the context of the main thread, which allows using GUI classes, COM objects, in one word, all those classes that can be created only in the context of the main application thread.

Test slots are divided into two categories, critical and non-critical. Critical test slot - is the key for the entire test class, if it fails the test, then further test slots will not be executed, control will be transferred to the next test class. To indicate the fact that the test is completed, the QS_TEST_COMPLETE; macro is used. Thanks to this approach, you can easily implement the testing of any asynchronous algorithm.

Test slots have a set of attributes. The task of the attribute is to indicate to the system that the slot should be executed with some special features. To minimize the declaration, the attributes are included in the name of the test slot itself, for example:

 #ifndef ATTRIBUTE_USAGE_EXAMPLE_H #define ATTRIBUTE_USAGE_EXAMPLE_H #include <QS_QuickUnit.h> class AttributeUsageExample : public QS_TestClass { Q_OBJECT private slots: void test_myTest_Fatal() { QS_BEGIN_TEST; //   QS_END_TEST; QS_TEST_COMPLETE; } void test_myTest_Repeat_15() { QS_BEGIN_TEST; //   QS_END_TEST; QS_TEST_COMPLETE; } void test_myTest_Fatal_Repeat_15() { QS_BEGIN_TEST; //   QS_END_TEST; QS_TEST_COMPLETE; } }; #endif //ATTRIBUTE_USAGE_EXAMPLE_H 

In the current version, only two attributes _Fatal and _Repeat_N have been implemented so far. The first indicates that the slot is critical, and the second indicates that the slot must be re-called N times in a row. By default, the test slot is not critical and runs only once. The macros QS_BEGIN_TEST and QS_END_TEST is a try catch block in the case of an exception interception, the test slot will be interrupted, and fixed as not having passed the test. There is also a group of test macros with them can be found in the developer’s manual. After describing the test class it remains only to register. Registration is recommended to be implemented in main.cpp

Quick Unit is not only suitable for testing under QT, but is also fully compatible with WIN32, ATL, MFC. According to the test results, the console displays a report on the passed tests, indicating the causes and points of error occurrence, the report also includes the execution time of the test slot, class.

We sincerely hope that QuickUnit will help in solving your problems. All suggestions and comments can and should be left here .

Source: https://habr.com/ru/post/128543/


All Articles