Use the test priority method to develop the user interface

Source: Internet
Author: User
Use the test priority method to develop the user interface

Author: cpluser
Web: http://www.enjoyimage.com
Email: enjoyimage@163.com.com
Blog: http://blog.csdn.net/cpluser/

DEMO code download

Keywords:
Test priority test driver development mock objects cppunit
 
1. Overview
Test first is the core idea of test-driven development (TDD). It requires that you write the test code based on the product code before writing the product code. In the unit test of test-driven development, automatic testing of GUI applications should be one of the weaknesses of test-driven development. Because the operations on the interface are completed by people, it is difficult to complete the unit automatic test in the GUI. Kent Beck mentioned this issue in its test-driven development.

This article uses an example to explain how to perform unit tests on the GUI in test-driven development. This is an example of video List Management in the Practical Guide for test-driven development (photoprinting) by David astels. The Chinese version of this book will be published soon in China. This document discusses and introduces multiple methods for developing this example. I will introduce one of them and write the code in the book in C ++ to facilitate the learning of C ++ friends, the class name and variable name should be consistent with the original book to facilitate reading the book's c ++ readers. We would also like to thank David astels for bringing us such a wonderful book.

The background of this article is cppunit1.9.0, Visual C ++ 6.0, and Windows2000 pro. If the description is incorrect, please criticize and correct it. If you do not have a certain understanding of cppunit, You can first refer to the author's article cppunit testing framework entry.

2. Requirement Analysis
For this video management application, we mainly implement the function of adding, deleting, and displaying the video list. Based on these requirements, we can draw a GUI sketch, 1:
Figure 1
The interface controls mainly include: A ListBox control that displays the list of all movies, an edit control that fills in the new movie name, a button control added, and a delete button control. As a result, our development goals are very clear.

3. Compile the UI test code
This part of the UI test code mainly tests whether each control is correctly generated and visible, and whether the label text of some controls is correct.
We inherit a class testwidgets from testcase for the test window, and add four tests to test ListBox, edit, Add button, and delete button respectively.
 
class TestWidgets : public CppUnit::TestCase
{
CPPUNIT_TEST_SUITE(TestWidgets);
CPPUNIT_TEST(testList);
CPPUNIT_TEST(testField);
CPPUNIT_TEST(testAddButton);
CPPUNIT_TEST(testDeleteButton);
CPPUNIT_TEST_SUITE_END();
public:
TestWidgets();
virtual ~TestWidgets();
public:
virtual void setUp();
virtual void tearDown();
void testList();
void testField();
void testAddButton();
void testDeleteButton();
private:
MovieListWindow* m_pWindow;
};
Among them, movielistwindow is a window class.
Let's take a look at one of the tests. Please refer to the comments in the code.
 
Void testwidgets: testaddbutton (){
// Obtain the BTN pointer
Cbutton * paddbutton = m_pwindow-> getaddbutton ();
// Check whether BTN is generated
Cppunit_assert (paddbutton-> m_hwnd );
// Check whether BTN is visible
Cppunit_assert_equal (true,: iswindowvisible (paddbutton-> m_hwnd ));
Cstring strtext;
Paddbutton-> getwindowtext (strtext );
Cstring strexpect = "add ";
// Check whether the label text of BTN is correct
Cppunit_assert_equal (strexpect, strtext );
}
 
Compile the test code. The compiler will give us some error information. This requires that the product code be written immediately for compilation.
The first product code to be implemented is the movielistwindow class.
 
class AFX_EXT_CLASS MovieListWindow : public CDialog
{
public:
MovieListWindow(CWnd* pParent = NULL); // standard constructor
CListBox* GetMovieListBox(){return &m_MovieListBox;};
CEdit* GetMovieField(){return &m_MovieField;};
CButton* GetAddButton(){return &m_AddBtn;};
CButton* GetDeleteButton(){return &m_DeleteBtn;};
void Init();
// Dialog Data
//{{AFX_DATA(MovieListWindow)
enum { IDD = IDD_MOVIELISTDLG };
CButton m_AddBtn;
CButton m_DeleteBtn;
CEdit m_MovieField;
CListBox m_MovieListBox;
//}}AFX_DATA
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(MovieListWindow) protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support //}}AFX_VIRTUAL
// Implementation protected:
// Generated message map functions //{{AFX_MSG(MovieListWindow) //}}AFX_MSG DECLARE_MESSAGE_MAP() };
 
In the movielistwindow class, we implement the required controls and some methods for these controls, such as getmovielistbox (). This article will not detail them here. Compile the test code and product code and check whether the code passes. If the Code fails, check the product code to make the compilation and test pass.

4. Write the control behavior test code
Next we should write the test code for clicking Add button and delete button.
Similarly, we inherit testoperation from testcase,
 
class TestOperation : public CppUnit::TestCase
{
CPPUNIT_TEST_SUITE(TestOperation);
CPPUNIT_TEST(testMovieList);
CPPUNIT_TEST(testAdd);
CPPUNIT_TEST(testDelete);
CPPUNIT_TEST_SUITE_END();
public:
void testMovieList();
void testAdd();
void testDelete();
public:
void setUp();
void tearDown();
TestOperation();
virtual ~TestOperation();
private:
static CString LOST_IN_SPACE;
CStringArray m_MovieNames;
MovieListWindow* m_pWindow;
MovieListEditor* m_pEditor;
};
 
You will find that a member variable movielisteditor * m_peditor appears in the testoperation class. movielisteditor is a management class used to save the video data and add or delete the video data. we will provide its implementation later.
Let's see what setup () has done,
 
Void testoperation: setup (){
// Create a movielisteditor instance
M_peditor = new movielisteditor ();
M_movienames.removeall ();
// Copy the video list in movielisteditor to m_movienames to prepare for subsequent tests.
For (INT n = 0; n <m_peditor-> getmovies ()-> getsize (); N ++)
{
M_movienames.add (m_peditor-> getmovies ()-> getat (n ));}
}

 
Let's take a look at the test of adding a video. Please refer to the code comment.
 
Void testoperation: testadd ()
{
// Copy a movie list
Cstringarray movienameswithaddition;
For (INT n = 0; n <m_movienames.getsize (); N ++)
{
Movienameswithaddition. Add (m_movienames.getat (n ));
}
Movienameswithaddition. Add (lost_in_space );
// Generate a window
Movielistwindow * pwindow = new movielistwindow (m_peditor );
Pwindow-> Init ();
// Enter the name of the new video.
Cedit * pedit = pwindow-> getmoviefield ();
Pedit-> setwindowtext (lost_in_space );
// Click Add BTN.
Cbutton * pbtn = pwindow-> getaddbutton ();
: Sendmessage (pbtn-> m_hwnd, bm_click, 0, 0 );
// Check whether a new video is added to the List control.
Clistbox * plistbox = pwindow-> getmovielistbox ();
Cppunit_assert_equal (movienameswithaddition. getsize (), plistbox-> getcount ());
// Check whether the shadow title in the list control is correct
Cstring strnewmoviename;
Plistbox-> gettext (plistbox-> getcount ()-1, strnewmoviename );
Cppunit_assert_equal (lost_in_space, strnewmoviename );
// Destruction window
Pwindow-> destroywindow ();
Delete pwindow;
Pwindow = NULL;
}
 
An error occurs after compilation. The main errors include:
A) We saved m_peditor in movielistwindow. We need to modify the constructors of the original movielistwindow.
B) There is no movielisteditor class.
The implementation of movielisteditor is as follows:
 
class AFX_EXT_CLASS MovieListEditor 
{
public: MovieListEditor(); virtual ~MovieListEditor();
public: virtual CStringArray* GetMovies(){return &m_arMovieList;};
virtual void Add(CString strMovie){m_arMovieList.Add(strMovie);};
virtual void Delete(int nIndex){m_arMovieList.RemoveAt(nIndex);};
private:
CStringArray m_arMovieList; };
 
Re-compile, has passed the. Run test, found in
 
CPPUNIT_ASSERT_EQUAL(MovieNamesWithAddition.GetSize(), pListBox->GetCount());
 
The test fails. The reason is that we are in the test code.
 
::SendMessage(pBtn->m_hWnd, BM_CLICK, 0, 0);
 
The message of clicking the button is sent to the Add button. However, in the movielistwindow window, we did not add the response function of the message. Therefore, we tested
Failed. Add the message response function quickly.
 
void MovieListWindow::OnClickAddButton() 
{
UpdateData();
CString strNewMovieName;
m_MovieField.GetWindowText(strNewMovieName);
if("" != strNewMovieName)
{
m_pEditor->Add(strNewMovieName);
m_MovieListBox.AddString(strNewMovieName);
}
}
 
Compile, test. Pass.
 
5. Mock objects
In the unit test of the delete operation, we encountered a problem that the video list data should be stored in a text file or database, if the tests we write depend on these actual files or databases, our tests will be subject to these external resources. Once the data in the file or database changes, it will inevitably affect our test code and generate incorrect test information. In the previous movielisteditor, we did not add some initialization data. Some problems may occur when testing the deletion operation.
 
Here, we introduce mock objects. Mock objects is used to simulate complex external resources (such as databases and network connections), so that the UI can test modules that depend on these complex external resources. For example, when testing a database-related module, we do not have to establish a real database connection, but just create a mock objects. The data required for the test exists in the mock objects. It can be said that mock objects provides us with a lightweight, controllable, and efficient model.
In this example, the addition or deletion of a video is related to file or database operations. Then we can use mock objects to isolate test code from files or databases. To use mock objects, follow these steps:

A) define an interface for external resources. (This interface can be extracted during the refactoring process ).
B) define a mock objects and inherit from the interface of external resources to implement the interface of external resources.
C) Create a mock objects and set its internal expectations.
D) Pass the created mock objects to the module to be tested for operation.
E) after the operation, compare the internal status of mock objects with the expected status.

Now we can follow this step to implement mock objects in this example. by restructuring the previous code, we can extract an interface movielisteditor:
 
class AFX_EXT_CLASS MovieListEditor 
{
public:
MovieListEditor();
virtual ~MovieListEditor();
public:
virtual CStringArray* GetMovies()=0;
virtual void Add(CString strMovie)=0;
virtual void Delete(int nIndex)=0;
};
 
Note that it is different from the movielisteditor defined above.
Next, we should define a mock objects, which is inherited from movielisteditor.
 
class mockEditor : public MovieListEditor
{
public:
mockEditor();
virtual ~mockEditor();
public:
virtual CStringArray* GetMovies(){return &m_arMovieList;};
virtual void Add(CString strMovie){m_arMovieList.Add(strMovie);};
virtual void Delete(int nIndex){m_arMovieList.RemoveAt(nIndex);};
private:
CStringArray m_arMovieList;
};
 
Then, set the first recognized value for the mock objects. We choose to perform this in its constructor.
 
mockEditor::mockEditor()
{
m_arMovieList.Add("Star Wars");
m_arMovieList.Add("Star Trek");
m_arMovieList.Add("Stargate");}
 
We added three clips for testing.
Next, we should pass this mockobjects instance to the module to be tested. Here is the UI to be tested (movielistwindow ).
 
m_pEditor = new mockEditor();
MovieListWindow *pWindow = new MovieListWindow(m_pEditor);
 
Finally, let's take a look at the modified method of adding a video to the new test,
 
Void testoperation: testadd ()
{
// Copy a movie list
Cstringarray movienameswithaddition;
For (INT n = 0; n <m_movienames.getsize (); N ++)
{
Movienameswithaddition. Add (m_movienames.getat (n ));
}
Movienameswithaddition. Add (lost_in_space );
// Generate a window
Movielistwindow * pwindow = new movielistwindow (m_peditor );
Pwindow-> Init ();
// Enter the name of the new video.
Cedit * pedit = pwindow-> getmoviefield ();
Pedit-> setwindowtext (lost_in_space );
// Click Add BTN.
Cbutton * pbtn = pwindow-> getaddbutton ();
: Sendmessage (pbtn-> m_hwnd, bm_click, 0, 0 );
// Check whether a new video is added to the List control.
Clistbox * plistbox = pwindow-> getmovielistbox ();
Cppunit_assert_equal (movienameswithaddition. getsize (), plistbox-> getcount ());
// Compare the internal data and expectations of mock objects
Cppunit_assert_equal (movienameswithaddition. getsize (),
M_peditor-> getmovies ()-> getsize ());
// Check whether the shadow title in the list control is correct
Cstring strnewmoviename;
Plistbox-> gettext (plistbox-> getcount ()-1, strnewmoviename );
Cppunit_assert_equal (lost_in_space, strnewmoviename );
// Compare the internal data and expectations of mock objects
Int nindex = m_peditor-> getmovies ()-> getsize ();
Cppunit_assert_equal (lost_in_space, m_peditor-> getmovies ()-> getat (nIndex-1 ));
// Destruction window
Pwindow-> destroywindow ();
Delete pwindow;
Pwindow = NULL;
}
 
Note that all the data tested here is in mockeditor. After adding the UI, you can also compare the internal status and expected status of mockeditor.
 
CPPUNIT_ASSERT_EQUAL(MovieNamesWithAddition.GetSize(), m_pEditor->GetMovies()->GetSize());
CPPUNIT_ASSERT_EQUAL(LOST_IN_SPACE, m_pEditor->GetMovies()->GetAt(nIndex-1));
 
The tests for other deletion operations are similar to those for adding. We will not detail them here. So far, we have completed the development of this GUI application. All tests are shown in Figure 2:


Figure 2

6. Source Code Description
The Code included in this article includes three projects: Movie, guitestfirst, and appmovielist. movie is the product code. guitestfirst is the test code. appmovielist is an application written using the product code output by movie. It inherits a new video management class named myeditor from movielisteditor. it mainly demonstrates how to use the movielisteditor interface we extracted. for example, you can implement cxmlmovielisteditor and caccessmovielisteditor. go to guitestfirst to open all these projects. Appmovielist run 3:


Figure 3

7. Summary
A) Implement the test-first Development Method for GUI applications. This is not necessary in test-driven development and can be selected based on the actual development conditions.
B) by introducing mock objects, we can isolate the test code from the complex external resources. At the same time, we can also extract clear interfaces from the existing code to make the code clean and available.

8. References
<Practical Guide to test-driven development (photoprinting)> David astels
<Test-driven development (Chinese version)> Kent Beck
<Endo-testing: Unit Testing with mock objects> Tim macinnon, Steve Freeman, Philip Craig

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.