четверг, 13 ноября 2014 г.

MindStream. How we develop software for FireMonkey. Part 3. Firemonkey + DUnit

Original post in Russian http://habrahabr.ru/post/241301/Table of contentsIn this post, I want to describe the process of transferring VCL code to FireMonkey. As far as I remember DUnit was a part of Delphi since Delphi 2009.
It has been written in the early days of VCL and although it allows you to test code written for FireMonkey (thanks to the console output), it does not have GUIRunner, to which many of us are accustomed to, because it's very fast and easy, for instance, to disable those tests that we do not want to run.

For those who are not familiar with DUnit, to create a DUnit project you should use File-> New-> Other-> Unit Test-> TestProject. Next, you should choose GUI or console version. After these simple manipulations, you will have a new project which will look something like (GUI, at least for my XE7) the following:

In general, my example shows how you can easily add unit-testing even to Delphi 7. All we need is to call DUnitTestRunner.RunRegisteredTests and add new files with TestCases to the project. In more detail unit-testing, using DUnit, is considered here.
Implementing GUIRunner for FireMonkey, I decided to repeat the guys who did the original DUnit. The first problem (I will not even tell that TTreeNode and TTreeViewItem classes are not compatible) that I encountered was here:

In FireMonkey Application.CreateForm method does not create a form. Yes, it is oddly enough.
Here is another link about that. It does not do what it says it does! I was, to be honest, a bit shocked by the fact that the method called "CreateForm" does not create it.
To solve this issue I create forms explicitly (l_GUI := TfmGUITestRunner.Create (nil);) and go further.
Now we need to build a tests tree based on TestCases we added for testing. If you notice, the construction of the form starts in RunRegisteredTestsModeless method.

I decided not to put this method into a separate module, as DUnit developers did, thus to use fmGUITestRunner, you must specify its module in the project code and actually call the appropriate method. In my case, the code is as follows:

The attentive reader will notice that we have not added any registeredTests, and it is never specified what kind of tests will be added. RegisteredTests is a global method of TestFrameWork, which is connected to our form; it returns __TestRegistry: ITestSuite (global variable);
The way how TestRegistry gets TestCases is out of the scope of this article. Moreover, that work has been done by DUnit developer. However, if readers have an interest in this topic, I'll reply to the comments.
So, back to the tree. A method to initialize the tree:

As you can see, in this method, we not only fill the tree, but also add information to each node, which of the tests corresponds to it. In order to get a test by a given node, let’s implement a method named NodeToTest:

My senior colleague and I don’t like the way I connect the tests and the tree. I understand why DUnit developer done this that way. DUnit has been developed long ago; Generics were not available. We will change it in the future. At the end of this post, I will write about our upcoming improvements and wishes.
So our tree is constructed, all the tests are inside FTests. Tests and the tree are connected. It's time to run the tests and interpret the results. To ensure that the form can do it, let's add to the form an implementation of the ITestListener interface described in TestFrameWork:

There’s nothing special here to explain. Although if you have some questions, I will give a detailed answer. In the original DUnitRunner after receiving a test result, it changes a picture of the corresponding tree node. I decided not to deal with pictures but change the FontColor and FontStyle for each node.
It looks like it takes a minute, but I spent a couple of hours, having dug through all the documentation

To summarize: at this moment, we have a fully working application for testing FireMonkey code using the usual GUIRunner. The project is open, so everyone can use.
Plans for the future:
Write a method to traverse the tree which will get a lambda function. The tree has to be around permanently, but the steps are different for each branch, so I think lambda function seems to be appropriate.
Comments and suggestions from my senior colleague:
To rebuild Tests and Nodes connection with TDictionary<TTreeViewItem, ITest> docwiki.embarcadero.com/Libraries/XE7/en/System.Generics.Collections.TDictionary
To add graphic indication of tests execution process. Add buttons: select all, unselect all, and so on, and the output of test results (execution time, the number of successful and failed tests, and so on).
To add the Decorator pattern to get rid of the GUIObject.
In the near future, we will start to cover our MindStream project with unit-tests, and also step by little step bring improvements to the Runner. Thanks to all who have read to the end. Comments and criticism are as always welcome in the comments.
Link to the repository.
p.s. The project is located at MindStream\FMX.DUnit path.
Links that were useful:
http://sourceforge.net/p/radstudiodemos/code/HEAD/tree/branches/RadStudio_XE5_Update/FireMonkey/Delphi/http://fire-monkey.ru/http://18delphi.blogspot.ru/http://www.gunsmoker.ru/
And of course
http://docwiki.embarcadero.com/RADStudio/XE7/en/Main_Page