wxWidgets & Google Test

13 August 2014
Not sure what motivated me to do this, but I decided to work out how to setup wxWidgets inside the Google Test framework. Suppose it is down to starting to see unit testing as a tick-list activity in the same way that I've long viewed source code control, and difficulties mocking user input for non-commandline interfaces being why I have shied way from it in the past. But motive is a discussion for another time..

For demonstration purposes, a simple program that copies text from one text entry box to another upon pressing the button between them will be used. The FRIEND_TEST macro allows a given test-case to have access to otherwise-private member variables, but on the whole there is nothing special. I don't know if there is a way to declare an entire test suite as a friend.

#include <wx/wx.h> #include <wx/uiaction.h> #include <gtest/gtest.h> class TestFrame : public wxFrame { wxTextCtrl *textIn; wxButton *button; wxTextCtrl *textOut; FRIEND_TEST(GuiTest, CopyTest); public: TestFrame(): wxFrame( NULL, wxID_ANY, "wxUnitTest", wxPoint(50, 50), wxSize(450, 340) ) { textIn = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 ); button = new wxButton( this, wxID_ANY, wxT(" => "), wxDefaultPosition, wxDefaultSize, 0 ); button->Bind(wxEVT_COMMAND_BUTTON_CLICKED, &TestFrame::OnButton, this); textOut = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 ); wxBoxSizer *boxSizer = new wxBoxSizer( wxHORIZONTAL ); boxSizer->Add( textIn, 1, wxALL, 5 ); boxSizer->Add( button, 0, wxALL, 5 ); boxSizer->Add( textOut, 1, wxALL, 5 ); this->SetSizer( boxSizer ); this->Layout(); this->Centre( wxBOTH ); } void OnButton(wxCommandEvent &WXUNUSED(event)) { this->textOut->SetValue(this->textIn->GetValue()); } }; class TestApp : public wxApp { public: TestFrame *frame; virtual bool OnInit() { frame = new TestFrame(); frame->Show( true ); return true; } };

Next, the GoogleTest overheads. Rather than using the wxINSTANTIATE_APP macro, underlying wxWidgets setup functions are called directly. Main thing is use of wxEntryStart() rather than wxEntry(), so that the wxWidgets event queue is processed on-demand, rather than entering the normal built-in main loop. The program entry point load the test framework, rather than wxWidgets.

class GuiTest : public testing::Test { protected: TestApp *app; virtual void SetUp() { char appname[] = "wxUnitTest.exe"; int argc = 1; char *argv[1] = {appname}; app = new TestApp(); wxApp::SetInstance(app); wxEntryStart(argc,argv); app->OnInit(); } virtual void TearDown() { //wxTheApp->OnRun(); app->OnExit(); wxEntryCleanup(); } }; int main(int argc, char *argv[]) { testing::InitGoogleTest(&argc,argv); return RUN_ALL_TESTS(); }

And finally, an actual test. A target control is programmatically given the focus, and then wxUIActionSimulator is used to inject keyboard input. The calls to wxYield() are there so that the wxWidgets message queue gets processed and controls get their chance to run their event handlers. For the test check itself, the input and output text boxes are compared.

TEST_F(GuiTest, CopyTest) { wxUIActionSimulator acts; app->frame->textIn->SetFocus(); wxYield(); acts.Text("Text"); wxYield(); app->frame->button->SetFocus(); wxYield(); acts.Char(WXK_RETURN); wxYield(); ASSERT_EQ("Text",app->frame->textOut->GetValue()); }

I'm not sure if it is possible to completely suppress the GUI windows from appearing, but given the circumstances I don't regard this as a major issue. Although Google Test is thread-safe on platforms that have Posix Threads, I am not aware of it running test cases in parallel.

Code coverage?

Unit testing often goes hand-in-hand with code coverage, and fortunately it is relatively to collect these for C++ programs under Linux. The downside is that it is also a bit messy. The first thing to do is create a binary by compiling with the --coverage flag, and then linking in GCov using -lgcov. After running the resulting binary, generate HTML coverage output using the following:

lcov -t "Test coverage" -o wxtest.info -c -d . lcov --remove wxtest.info "/usr/*" -o wxtest.info lcov --remove wxtest.info "wx/*" -o wxtest.info genhtml -o html wxtest.info

The two lcov --remove calls strip out code coverage of the system and wxWidgets libraries, as these are of no real interest. The way these have to be stripped out rather than not included in the first place is one of the ways in which code coverage is a bit messy, although the source-release (as opposed to the fossilised Ubuntu release I used) has the --no-external parameter that does much the same thing. Branch and function statistics also seem a little inaccurate at times, due to various things that gcc does in the background.