Using a data driven data source for an inner test case

Ranorex Spy, Recorder, and Studio.
dochoa
Posts: 29
Joined: Tue Jul 16, 2013 11:36 pm

Using a data driven data source for an inner test case

Post by dochoa » Thu Jun 19, 2014 10:53 pm

Hello,

I am a tester with virtually no knowledge of code. I have some developers on my team but the less they have to support me the better. Ideally, the hope is the solution for the following issue can be done without writing code.

Using:
Operating System: Windows 7 Professional Service Pack 1
Ranorex Version: 5.0.1.17706
Browser Version: IE10 v10.0.9200.16921


In this screen shot you can see the structure of the test case I am creating (not finished yet):
TestCaseScreenShot.JPG
TestCaseScreenShot.JPG (100.9 KiB) Viewed 2791 times
Here is what we plan to do.
1) Create a data source for the modules at the top of the list
2) Create a data source (or sources if needed) for the modules in the inner test case at the bottom of the list.
3) As the test case iterates through the rows of the first data source we want it then to iterate through a data source specific to the row that is being used in the other data source.

Another explanation:
What we are creating here is media items that have varying numbers of language elements assigned to them. Some of the media will have 6 languages while some have 3.
So the flow would be:
1) To create Movie 1 the test case runs through the first steps using the first data source to populate the data (we have to also skip empty values but there is another post from someone else about that we can reference).
2) When the test case gets to the inner test case we want to enter values from another data source based on the Parent title that was entered in the surrounding test case.

I'm thinking that we have 2 options here:
1) Make multiple Data Sources for the inner test case
2) Make one data source for the inner test case that only use rows that match the outer test cases current iteration

For Option 1 I think we would have this:
MultipleDataSources.JPG
MultipleDataSources.JPG (76.2 KiB) Viewed 2791 times
In that example the outer test case runs the test for Movie 1, then when it gets to languages it knows to use the Movie 1 language data source. When it iterates through Movie 2 and gets to languages it then knows to use the Movie 2 language data source.


For Option 2 I think we would have this:
SingleDataSource.JPG
SingleDataSource.JPG (71.2 KiB) Viewed 2791 times
In that example the outer test case runs the test for Movie 1, then when it gets to languages it knows to use only the rows in the second data source that match the parent title being entered by the first source for Movie 1. When it iterates through Movie 2 and gets to languages it knows to use only the rows in the second data source that match the parent title being entered by the first source for Movie 2.

Which is the best solution? Can I do this with minimum or no code?

I have also attached a SnapShot of the page this test is automating.

krstcs
Ranorex Guru
Posts: 2683
Joined: Tue Feb 07, 2012 4:14 pm
Location: Austin, Texas, USA

Re: Using a data driven data source for an inner test case

Post by krstcs » Fri Jun 20, 2014 2:40 pm

You have found exactly the same problem that I ran into with many of our products. :D Wall-of-text incoming...

There is a way to do what you want, but it is complex and requires heavy use of SQL Server for data manipulation (dynamic queries) and the ability to modify the SQL data connectors in Ranorex at run-time.

Also, I highly recommend that you make your modules as small as possible (which it appears you have done). This will allow you to re-use them in other tests, which makes management of actions much easier. So, you would have a Click_OKButton recording module, that only clicked the OKButton repo object, etc. You can then use module groups to group modules that are commonly used together (note that groups contain no control-flow (if-then, for-each, etc.) logic).

Naming conventions are also important. Mine may not be best for you, but here is what I do. Let's say am doing customer purchases:
The stored procedure would be named "GetPurchaseInfo_for_CustomerID" and would take CustomerID as a parameter (I also have a TEST_TYPE value I pass in to allow for data configuration at run-time).
The code module would be "Set_PurchaseInfoSQL_for_CustomerID" because it is setting the SQL query for the "PurchaseInfoSQL" SQL data connector, using the passed in CustomerID test variable.


Having said all that, let me lay it out for you:

First, you will want to have all of your data in SQL Server (Express is free and works great). You will want to setup Stored Procedures (SPs) (under the Programmability folder in SQL). The precept is that SQL should handle ALL data manipulation, and Ranorex should only ask it for the information when Ranorex needs it. I have all of my SPs set with default values for all parameters so that they will return the table structure, but no data. This will allow you to bind the data source to variables without having any data in the table, making it a way to control program flow if needed. Just think of the test cases as for..each loops such that they read "for each row in the data source do these modules (or sub-test cases)".

Second, you will need to create code modules (not Recordings, you don't need all the overhead) that will be data connector manipulators. They will set the SQL data connector's query at runtime using variables passed in. To create them, use the following process:
1. Right-click the project you want to add the modules too and select "Add -> New Item".
2. In the "New File" dialog, select the "Code Module" item, and name it whatever you want.
3. In the new file, right-click anywhere and select "Insert Module Variable" and name the variable the EXACT name you are using for that data in the database (in the example above, I would make a CustomerID variable).
4. At the bottom of the Run() method you would add an action that changes the SQL query.

Example (note the use of string.Format() to make readability better, versus ("select * from table where customerID=" + CustomerID + ", @TEST_TYPE=" + TEST_TYPE), but you can do that if you like it better):

Code: Select all

    public class Set_BillingInfoSQL_for_CustomerOrderID : ITestModule
    {
        /// <summary>
        /// Constructs a new instance.
        /// </summary>
        public Set_BillingInfoSQL_for_CustomerOrderID()
        {
            // Do not delete - a parameterless constructor is required!
        }

        string _TEST_TYPE = "LOCAL";
        [TestVariable("3D402B20-7CD4-43E0-92A2-2A85420905FF")]
        public string TEST_TYPE
        {
        	get { return _TEST_TYPE; }
        	set { _TEST_TYPE = value; }
        }
        
        string _CustomerOrderID = "0";
        [TestVariable("8F42665A-6990-408D-B7C1-8FE4D4B1C1DA")]
        public string CustomerOrderID
        {
        	get { return _CustomerOrderID; }
        	set { _CustomerOrderID = value; }
        }
        
        /// <summary>
        /// Performs the playback of actions in this module.
        /// </summary>
        /// <remarks>You should not call this method directly, instead pass the module
        /// instance to the <see cref="TestModuleRunner.Run(ITestModule)"/> method
        /// that will in turn invoke this method.</remarks>
        void ITestModule.Run()
        {
            Mouse.DefaultMoveTime = 300;
            Keyboard.DefaultKeyPressTime = 100;
            Delay.SpeedFactor = 1.0;
            
            TCS_LIB.Data.SetupSqlDataConnector("BillingInfoSQL", string.Format("exec GetBillingInfo_for_CustomerOrderID @CustomerOrderID={0}, @TEST_TYPE=N'{1}'", CustomerOrderID, TEST_TYPE));
        }
    }
Note, I have a method in a library that does the heavy lifting for me. Here is the insides of that method (it's really simple, but I use it in a lot of places, so it is easier to use a lib method):

Code: Select all

        public static void SetupSqlDataConnector(string dataCacheName, string queryString) {
            ((SqlDataConnector)DataSources.Get(dataCacheName).Connector).Query = queryString;
        }
5. Now, You need to drop this code module in your test suite just before the test case that uses the data connector you are changing, binding the variables as well. Make sure that the data set from the PARENT (or Parent's parent, etc.) test case has the data in it you need for this module. In my case, my parent test case would have the CustomerID (an internal TEST DB ID, for test use only).

Finally, you will need to setup the data connector. This is not hard, but since it is unique for each test suite, it can get cumbersome. (I would love for Ranorex to implement data connectors as modules just like everything else -- Hint, Hint Ranorex team :D ) Setup the connection string and then the query. Do not use the "Create query" obtion, just type in the query you want. Make sure the query in the data connector calls the same stored procedure you created earlier (that way if you change the SP, you don't have to remember to change the query in Ranorex). It should look like:

Code: Select all

exec GetBillingInfo_for_CustomerOrderID
Note that I don't pass any parameters since we set defaults. This makes it easier to create the data connector.


And, that's it... Simple... :D

Let me know if you need help with it.
Shortcuts usually aren't...

dochoa
Posts: 29
Joined: Tue Jul 16, 2013 11:36 pm

Re: Using a data driven data source for an inner test case

Post by dochoa » Fri Jun 20, 2014 4:44 pm

Thank you very much Krstcs. That is very useful information and I will review it with my developers.