More interesting still would be the correlation between "Testing First" and unit test quality. Ask yourself this, which unit tests are going to be better, the ones created before you've implemented the functionality or those created after? Or, considered another way, at which point do you have a better understanding of the problem domain, before implementing it or after? If you're like me, you understand the problem better after implementing it and can thus create more relevant tests after the fact than before. I know better where the important edge-cases are and how to expose them and that could mean that my tests are more robust.
Misc musings about software development, agile, TDD, C++ and explorations in other programming languages.
2012-08-26
Why should you learn and use TDD in the first place?
2012-08-12
Presenter First with Qt
Somewhat recently a friend of mine sent me an article on the Presenter First pattern - a 'recipe' to build applications using the Model-View-Presenter (MVP) pattern with Test Driven Development (TDD). The article seemed to be tuned against C# and Java, so I wondered how I would apply Presenter First with Qt and C++. This is a short experiment with implementing a UI with Qt, using the Presenter First pattern.
The idea with the 'Presenter First' pattern, is to implement the Presenter
first in a test-driven fashion. In the MVP-pattern, the presenter only see an abstract representation of the view and the model, listens to events from them and glue their behaviors together. In the PresenterFirst formulation from the aforementioned article, model and view are interfaces. Since we are using Qt, we want to utilize the signal and slot mechanism so both classes would be abstract classes that somehow inherits from QObject
. For starters the Model can inherit QObject
, and the View can inherit QWidget
. Both will be abstract with only pure virtual functions and slots. While going through this blog-post I will put emphasis on the test-code and the Presenter code, and for the most part regard the View and Model code as 'given'. As you will see, the tests we implement turns out to correspond very closely to the usecases we are implementing and we are able to test all aspects of the behaviour of the application under user interaction.
The working example will be a simple "editor" with two controls: a list-view to list available entities by name on the left, and a text edit-view for editing the content associated with the name on the right. Lets imagine that the content are equations, so we call it an equation editor. When writing this, I wanted to try using a component from the Qt itemviews, because they are intentionally written in a way that merges the View and Controller components of a MVC-design. 1 This makes them a less-than-perfect fit with the MVP and PresenterFirst, but we'll see if we can make due never the less.
The ui should look something like this. Equation names in the left listview, the actual equation in the textedit on the right.
When interacting with the editor, we would like to implement the following user-stories:
- When I select equation named E in the dropbox, the equation should show in the equation editor
- When I click 'new equation', a dialogue should show for me to select a name for the new equation, and the new equation should afterwards show in the item-view on the left
- When the user edit the equation-text for an equation X, then select a different equation Y, and then again select the recently edited equation X, the originally written text for equation X should be displayed in the editor
- When I click on a selected equationname, I should be able to edit it, and only the new name should be used to reference the equation
It won't be an equation editor yet after implementing them, but we'll be off with a good start! It doesn't matter which of these stories we implement first, even though the last story could benefit somewhat from doing the first one in the list first.
Let's dive right in. We start with the add-new-equation story. The first tests for the presenter could thus look something like this:
void PresenterTest::test_addNewEquation(){ MockEquationView * v = new MockEquationView; MockEquationModel * m = new MockEquationModel; Presenter p( v, m ); QString equationName("new equation"); v->user_createNewEquation( equationName ); m->verify_newEquationCreated( equationName ); v->verify_modelChangedRecieved( 1 ); }
This reveals the basic MVP design. The Presenter have references to an abstract Model and an abstract View, and listens for updates via some sort of event-model to glue their behaviours together.
The overall design of Model View Presenter, and presenter first. The presenter only knows about the abstract representations of view and model, and listens to events.
During the development we will implement the abstract classes EquationView
and EquationModel
, with their mock-objects MockEquationView
and MockEquationModel
. The 'View' here represents the target UI form, while the 'Model' stores equation-names and equations and ties them together. After we are done, we add ConcreteEquationView
and ConcreteEquationModel
- the latter developed with the help of TDD.
We drive the test by telling our fake view that the user want to "create a new equation", and afterwards we verify that there was a new equation created in the model and that the view got an update about changes in the model.
To see what's going on better, I'll prefix all the functions on the fake version of the view used to "simulate" user interactions in order to drive the presenter and the model with "user_" - so we don't confuse the method-names from the test-double with the methods of the "real" view. Also - I'll roll my own mocks, I will not use a mocking library in these examples (even though I could have benefited from using something like Google Mock.)
How should we implement this behavior? If the presenter listen to a signal from the view that asks for creation of a new equation we would be in good shape!
Presenter::Presenter(EquationView* v, EquationModel* m) : view_(v), model_(m) { connect(view_, SIGNAL(createNewEquation()), this, SLOT(addNewEquation())); }
This makes it obvious where the events come from. It also clarifies a presenter-first idiom: We 'drive' the Presenter
through simulated user behavior, created by programmatically interacting with a fake view-class.
Note that the createNewEquation
and addNewEquation
signal-slot pair have no arguments, but from the user story we are expecting a specific name for the equation to be created - the story even specify that a dialog should pop up. We can't have dialogues popping up in our automated unittests - there's no user on the other end - so I want to stub out that behavior. There are many ways to achieve this. In the example article that inspired this blog post, they fired an event to show the dialogbox. Instead in this example I just ask the view to create a new equation name for me through createNewEquationName
, and let ConcreteEquationView
pop up the dialogue, get the name, and return this from an interface function. For the test-view, I implement createNewEquationName
so it returns the name passed with user_createNewEquation()
.
void Presenter::addNewEquation() { QString eqname = view_->createNewEquationName(); model_->addNewEquation(eqname); view_->modelChanged(); }
The code above should be sufficient to make our test pass. The modelChanged()
function can be a pure virtual function or slot on the View object, and in our MockView we just register that it has been called - we don't need to introspect the view in order to know that this test passes.
Below is the state of EquationView
, MockEquationView
and EquationModel
at this point. The verification code in MockEquationModel
is similar to that of MockEquationView
.
class EquationView : public QObject { public: Q_OBJECT; EquationView(QObject * parent = NULL); virtual ~EquationView(); virtual void modelChanged() = 0; virtual QString createNewEquationName() const = 0; signals: void createNewEquation(); }; class MockEquationView : public EquationView { public: Q_OBJECT; MockEquationView(QWidget * parent = NULL) : modelchanged(0) { } virtual ~TestEquationView() {} void user_createNewEquation(QString equationname) { createNewEquationName_ = equationname; emit createNewEquation(); } virtual QString createNewEquationName() const { return createNewEquationName_; } virtual void modelChanged() { modelchanged++; } void verify_modelChangedReceived( int correctupdatecount ){ QVERIFY(modelchanged == correctupdatedcount); } private: QString createNewEquationName_; int modelchanged; }; class EquationModel : public QObject { public: Q_OBJECT; EquationModel(QObject * parent = NULL); virtual ~EquationModel(); virtual void addNewEquation(QString equationname) = 0; };
This completes the first story. Next we enable selection of different equations in the listview. To be certain that this story is complete, we need a model with several items, and we need to verify that the correct equation text is set in the text-edit after changing the index.
void PresenterTest::test_indexChanged() { MockEquationView * v = createView(); EquationModel * m = new MockEquationModel; populateModel(m); // adds three equations. Presenter p ( v, m ); int indexnr = 1; // indexnr < number of equations defined in Model. v->user_changeIndexTo(indexnr); v->verify_currentEquationIs(m->getEquation(indexnr)); }
This drives a little bit of design. We expect that, when we change equation in the listview, the view must be told what equation to put in the equation text-box. So it seems that the Presenter
must listen to some change-of-index event, be able to fetch the equation for this index from the model, and then pass the correct text from the model, to the view. The presenter needs a slot like this:
void Presenter::changeEquationIndex(int equationidx) { v->setEquationText(m->getEquation(equationidx)); }
The view class need a corresponding signal that we can emit from MockEquationView::user_changeIndexTo(int indexnr)
(or from the concrete view when we implement that.) This signal-slot pair needs to be connected in the Presenter
constructor.
Presenter::Presenter(EquationView* v, EquationModel* m) { connect(v, SIGNAL(createNewEquation()), this, SLOT(addNewEquation())); connect(v, SIGNAL(equationIndexChanged(int)), this, SLOT(changeEquationIndex(int x))); }
Our fake view can then verify that it got the correct equation text. Our test passes. Now we know that the View have been told to display the correct data both in the listview and the equation editor. The story is complete.
The third story, and the third test, is for the edit equation story. When we edit the equation text in the text-editor on the right in the view, the text should be sent to the model.
void PresenterTest::test_editedTextIsSetInModel() { MockEquationView * v = new MockEquationView; MockEquationModel * m = new MockEquationModel; populateModel(m); // adds a couple of name-text pairs QString newText = "My new Equation."; int activeIndex = 1; v->user_changeIndexTo(activeIndex); v->user_editEquationText(newText); m->verify_equationText(activeIndex, newText); }
In the test we simulate that the user chooses an equation through user_changeIndexTo
, and then simulate that the user_editEquationText
to something. Then, in order to verify that our Presenter
works as we want it to we check that the equation-text sent to the model is correct.
In order to make this test pass, we need two things: A signal from the view that tells the presenter that text have changed, and a corresponding slot on the presenter that updates the equation-text associated with the current active equation. That means we would need to keep track of what the current equation is. The tracking of current state belongs in the presenter.
Presenter::Presenter(EquationModel * m, EquationView * v, QObject * parent) : QObject(parent), model_(m), view_(v), currentEquationIndex(0) { connect(v, SIGNAL(createNewEquation()), this, SLOT(addNewEquation())); connect(v, SIGNAL(equationIndexChanged(int)), this, SLOT(changeEquationIndex(int x))); connect(v, SIGNAL(equationChanged(QString)), this, SLOT(currentEquationTextChanged(QString))); } void Presenter::changeEquationIndex(int equationidx) { currentEquationIndex = equationidx; v->setEquationText(m->getEquation(currentequation)); } void Presenter::currentEquationTextChanged(QString equation) { m->setEquation(currentEquationIndex, equation); }
We added the connection in the constructor, added currentEquationIndex
as state in the presenter, and implemented setActiveEquation
. A handful of lines of code, and our test is passing.
No for the final test: Editing the name of an already added equation. So far we've only used signals and slots from Qt, and we have talked about how new equations come to be (and that this requires the View to receive a modelChanged
signal) and how the text in the equationview is updated. But we have not talked about how the listview with equation-names to edit is populated - this have been a detail we could just gloss over so far. Now that we should be allowed to change the name of the equation in the listview, we need to supply an editable model from Qt's modelview components to the view and then test that the data stored in it is updated. Granted, we could make this work without using an QAbstractItemModel
- we could somehow have kept the names in our model in synch with what's in the view by setting a QStringList
of equation-names every time the model changes or adding and removing names by some other means. This would increase the amount of logic we would have to add to the concrete View-implementation - the code we are trying to keep as minimal as possible. By using an QAbstractItemModel
on the itemview and simulate the interaction from the MockView
, everything is more or less automated from the ConcreteEquationView
's viewpoint.
For our next test then, the view must change the name for an item, and we need to check that the name is updated in the model.
void PresenterTest::test_equationNameIsChanged() { MockEquationView * v = new MockEquationView; MockEquationModel * m = new MockEquationModel; QString originalname = "Name0"; int idx = m->addNewEquation( originalname, "Some text"); Presenter p(m, v); QString newname = "Name1"; v->user_changeEquationName(idx, newname); QCOMPARE( m->getEquationName(idx), newname ); }
The key driving function of the test - user_changeEquationName
- simulates the behavior of the listview by first creating a correct QModelIndex
for the name and then call abstractlistmodel->setData(index,data)
to update the name stored in the model.
There are several valid alternatives for how to get an QAbstractListModel
from our EquationModel
:
- The Presenter can act as a proxy and inherit from
QAbstractListModel
and get the necessary data from it'sEquationModel
reference. - We could create a concrete
EquationListModel
that inherit fromQAbstractListModel
and which take anEquationModel
pointer as a constructor argument and sendsgetData
andsetData
calls to it (again representing our model by proxy.) - The
EquationModel
it self could inherit fromQAbstractItemListModel
, and be both.
What matters most at this point, is that these are the options. There are three. Not four or five. A getter or factory-method on EquationModel
that returns an QAbstractItemModel
is a jolly bad idea. 2
In order to quickly make the test pass, I chose the last option - the EquationModel
inherit from QAbstractItemListModel
- then I only need to add a setData
function to it, and use it to update the equation-name.
The constructor of Presenter
now only need to be updated so that it passes the EquationModel
(as a QAbstractListModel
) to the EquationView
, and after a correct implementation of setData
my test passes.
Presenter::Presenter(EquationView* v, EquationModel* m) { connect(v, SIGNAL(createNewEquation()), this, SLOT(addNewEquation())); connect(v, SIGNAL(equationIndexChanged(int)), this, SLOT(changeEquationIndex(int x))); connect(v, SIGNAL(equationChanged(QString)), this, SLOT(currentEquationTextChanged(QString))); v->setModel( (QAbstractListModel*) m ); // cast for emphasis }
Voila, all user stories implemented test first, without ever having to open an application with a Widget. So far so good.
There are several observations we can make while wrapping up
here. Notice first how all the tests "simulate" the user
interaction. The driving functions get names that are very simmilar
to the user stories: user_editsEquationText
is a good example. Also,
notice how none of the tests actually calls functions on the
Presenter
, save from the constructor. The tests only calls functions
on the mocks, and the behavior of the Presenter is then implicitly
verified. All Presenter slots and functions can in other word be
private. Both of these observations are in line with the experiences
reported in the previously mentioned article.
I also want to emphasis how the tests are agnostic to many of our design decisions. Notice for example how test_changeEquationName
is totally ignorant to our actual design choice - we can choose any one of the three alternatives proposed for getting a QAbstractItemModel
, and the test still stays the same. Presenter First drives you to implement your tests on a comfortable distance from your design, where they support your design decisions without geting into the way when refactoring.
When using Presenter First with Qt, all UI-classes in most cases only need to relay signals from the relevant ui-components to the Presenter via it's own signals, and implement the slots to set the data to display in the correct ui components. A test of the GUI simply need to verify that it is hooked up - e.g. that pressing a button makes the view emit the correct signal - as the test-driver for the presenter tests the actual functionality behind.
There is a full example with a view implementation in addition to the tests at bitbucket. It's not an equation editor yet, but as far as being an example of Presenter First with Qt, it suffice. Thank you for reading!
Footnotes:
1 This is, btw, the design choice in Qt I have the biggest beef with as it lures the unwary developer to add tons of behavior in view classes. The result is often classes that mix responsibilities and are difficult to test.
2 I also might mention here that I usually just avoid the QTreeWidget
by default. In order to avoid the duplication of data and the efforts needed to keep the QTreeWidgetItems
in synch with the underlying datamodel I like to implement a QAbstractItemModel
around my data instead.