I have been struggling for a while on how to make desktop applications I am working on with Qt unit testable. In general there are many techniques presented that achieve the goal of making the UI part of an application so thin that it doesn’t need to be be unit tested while the business logic behind the UI is separated and can be easily unit tested. Most of these techniques work beautifully in implementations that use languages with a big arsenal of tools targeted to unit testing and dependency injection like Python and Java that I have experience with.
In Qt and C++ the whole process of making an UI testable has it’s quirks (Qt views need to have a parent – child relation, there is no garbage collection etc.). I have found that the approach of “Humble Dialog” is the one that gives me most benefits:
- The view is the front-man (what is exported from a model – view – presenter (controller) triad is the view) so it plays well with Qt that also has this approach and is more comprehensible from an audience that is not exposed to the pattern.
- All the wiring of the triad happens in the view, so all parts that are not supposed to be unit tested are in the same place and I can in most places avoid setting up factories for wiring up and instantiating the triads.
- The messages between triads are easily delivered since the whole setup resembles a lot the typical mixed UI/business logic that seems to be the convenient way of creating applications with a UI and is the one that most GUI toolkits support.
An example implementation of something as simple as a messagebox presenting some text could be (from a question I posted on Stack Overflow):
class IView {
public:
IView(){}
virtual ~IView(){}
virtual void showResult(const QString &text)=0;
};
class Presenter {
public:
Presenter(IView *view){
m_View = view;
}
~Presenter(){}
void buttonPressed(){
QString text;
// Evaluate text
m_View->showResult(text);
}
private:
IView *m_View;
}
class MyView : public IView , public QDialog {
public:
MyView(){
m_Presenter = new Presenter(this);
m_Button = new QPushbutton(this);
m_Label = new QLabel(this);
// Ui event handled inside view but then directly
// propagated to the Presenter
connect(m_Button,SIGNAL(clicked()),this,SLOT(buttonPressed()));
}
~MyView(){
delete m_Presenter;
// Qt will automatically delete m_Button and m_Label;
}
void showResult(const QString &text){
m_Label->setText(text);
}
protected slots:
void buttonPressed(){
m_Presenter->buttonPressed();
}
private:
Presenter *m_Presenter;
QPushbutton *m_Button;
QLabel *m_Label;
}
class TestView : public IView {
public:
TestView(){}
~TestView(){}
void showResult(const QString &text){
m_LabelText = text;
}
QString getResult(){
return m_LabelText;
}
private:
QString m_LabelText;
}
// Test code
TestView view;
Presenter presenter(&view);
presenter.buttonPressed();
EXPECT_EQ(view.getResult(),"Expected Result");
// Production code
MyView view;
view.show();
The main advantage I see from following this approach is that if I wanted to i.e. instantiate an new model – view – presenter triad after some user action I could perform it inside the view instantiating it easily without the need for a factory and secondly from the “outside” the triad seems to be just a view and not a full blown MVP triad that makes it play well with Qt.
I ‘d like others to share their thoughts on this, since for me settling with this approach has come after a lot of trial and error.