Testing proxy models gets easier. Again.

I just pushed a few commits to the tests section of kdelibs which make it easier to test proxy models.

I have written before about testing of proxy models, but things have progressed greatly since then. Aside from creating a unit test suite, I also created a small application for testing proxy models, imaginatively called proxymodeltestapp.

The idea was to have a visual indication of whether the proxy models used in Akonadi were working properly. Those proxies are all implemented generically so that they can be used with any model, in this case DynamicTreeModel which I wrote about before. Having full control over the contents of the DynamicTreeModel by using various commands meant that I could tackle various bugs in the proxies as they came up by recreating models and scenarios in the proxymodeltestapp, such as “starting with a complex tree, we move these items from here to there. Then the application crashes.”. Having a way to reproduce those scenarios in a Short, Self Contained, Correct, Example, away from the additional Akonadi and application code has proved very useful. There is even a widget to drive the DynamicTreeModel in the same way as it is driven in unit tests. Again, having a visual representation of what is going on and how the proxy should react takes some of the pain out of writing and fixing unit tests for the proxies.

It’s all in the interpretation

The first of todays interesting commits adds an interpreter to DynamicTreeModel. The two most annoying parts of writing tests for proxy models are setting up an initial condition, and inserting some new rows, some of which have child items themselves. Both issues have the same root cause: The DynamicTreeModel did not have any easy to use command to insert a tree or sub-tree into the model. The old solution was to define some fragments in a ModelInsertWithDescendantsCommand (catchy name as always), and when the command was executed, the fragments would be inserted at once, creating a new subtree. This kind of worked, but the code was difficult to read and implement, both counter-productive to unit testing.

The new interpretation method looks something like this:

insertCommand->interpret(
  "- 1"
  "- 2"
  "- - 3"
  "- 4"
  "- 5"
  "- - 6"
  "- - - 7"
  "- 8"
);
insertCommand->doCommand();

which will of insert 5 new rows in the model, some of which have children. Much better for writing unit tests. The parsing logic is very simple, and each of the items in the tree does not need to be a sequential number. They can all be the same number, letter or series of gibberish if it makes it easier. There’s even a #define to dump a nice numerical tree to stdout if you want to create a readable version from a gibberish version.

Simultaneous interpretation

Of course the interpret feature is not limited to writing unit tests. Through the use of the new DynamicTreeWidget, proxymodeltestapp can be used to quickly create a model for testing purposes without recompliling or even restarting the application.

DynamicTreeWidget View and Edit tabs

On the left is the “View” tab of the DynamicTreeWidget, which contains the DynamicTreeModel in a QTreeView. By clicking on the “Edit” tab, we get an editable textual representation of the tree. We can move, add and remove items in the text view, and when we switch back, the “View” tab is already updated with the new edited version. We can also use separate controls in the DynamicTreeWidget to insert or remove items in the model, or move them with drag and drop, and the text version is instantly updated. This is a very simple way to create and manipulate the model for proxy (or view) testing purposes and allowed the removal of a lot of code in the test app.

Scriptable descendants

The last interesting commit for today (there will be plenty more days) combines a new proxy model with QtScript. I haven’t written about the KReparentingProxyModel before because it will hopefully be part of kdelibs 4.5, but not 4.4. The idea is to define in a proxied model a completely different structure than is present in the source model. It could be used for example to turn a list of emails into a threaded tree of emails in KMail, or a list of todo tasks into a tree of tasks and subtasks in KTimeTracker. It could be used to flatten a tree into a list for use in a combobox or similar widget, or to turn a particular tree into another tree of a different structure for a reason I can’t imagine. On the whole, it should be useful for Akonadi when fully working.

There is only one method in the API:

virtual bool isDescendantOf(const QModelIndex &ancestor, const QModelIndex &descendant);

Consumers of the class reimplement that method to return whether descendant should be a descendant of ascendant in the proxy model in much the same way that you might implement QSortFilterProxyModel::lessThan. By using a little bit of QtScript magic, I can define the structure in a proxy at runtime in the proxymodeltestapp.

Scripted reparenting proxy model 1

Scripted reparenting proxy model 2

Scripted reparenting proxy model 3

Nice, no?

3 Responses to “Testing proxy models gets easier. Again.”

  1. brueckevomkwai Says:

    Really nice! I wish your KReparentingProxyModel class would be part of Qt. That would have saved me a lot of time…

  2. Eduardo Robles Elvira Says:

    Honestly this stuff is amazing =) I hope I get some time to work on all this again.

  3. Proxy models are really really great (They crash!) « Steveire’s Blog Says:

    […] By steveire Developments are ongoing in the quest for better testing methods for proxy models. I wrote before about testing a proxy model by visual inspection in the proxymodeltestapp. For identifying the […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


%d bloggers like this: