a developer's notes – a semi-technical web development BLOG

October 31, 2013

Performance Load Testing with Visual Studio 2012 – Validating your data source file

Whether you are performance testing a web application or web service, you don’t want bad input data included in your test. This may skew your HTTP response times or simply add to the list of Errors that are captured during your performance test. Ideally, we want to have all clean and working input data so that any errors reported are not because of the data the test is using for a HTTP request. But sometimes we have data that has not yet been tested. So how do we clean up this data so that our test only uses input data that has been confirmed to work? One way to solve this problem is to use Visual Studio web tests to filter out any data issues from your data set using the following techniques.

In this post, I have created a simple Web Test plug-in that will write to a text file onto your local machine. This plug-in supports CSV as well as XML** data sources. (**There is one extra step with the XML data source that I will go into later.)

Create a Web Test

Here is a simple web service test that sends a request to verify if an account exists. I add this plug-in to my web test to illustrate how it works.

webtest

The data source this test uses is a CSV file with only one field, AccountNumber. Here, I have intentionally added two bad accounts.
If you are using a CSV, the data file might look like this:

AccountNumber
73326436
Xxx (bad)
15280938
Xxx (bad)
15481994
15481994
15496778

If you are using XML, the data file might look like this:

<Root>
  <Row>
    <AccountNumber>73326436</AccountNumber >
  </Row>
  <Row>
    <AccountNumber>xxx</AccountNumber >
  </Row>
  <Row>
    <AccountNumber>15280938</AccountNumber >
  </Row>
  <Row>
    <AccountNumber>xxx</AccountNumber >
  </Row>
  <Row>
    <AccountNumber>15481994</AccountNumber >
  </Row>
  <Row>
    <AccountNumber>15481994</AccountNumber >
  </Row>
  <Row>
    <AccountNumber>15496778</AccountNumber >
  </Row>
</Root>

Select All Columns in your data source file

It is important to note that the properties on your data source should “Select all columns.” Just highlight the data source file and view its properties.

webtest_highlightData

dataProperties

Doing this, will allow all the fields from your data source file to be added to the webtest.Context collection. Though in this scenario we only have one field, it will work with multiple fields.
Here is the data source field in the Context collection.

webtestContext

Edit a .testsettings File

In Visual Studio’s Solution Explorer, choose a Load and Web Test setting. It should be under the Solution Items folder and has an extension of *.testsettings.
Double click on your test setting file to Edit. Select Web Test on the left navigation, and choose the radio button to “One run per data source row.” This will allow your web test to run one iteration for each row of data in your data source file.

testSettings

Execute Web Test for All Data

Now run your test. Notice iterations 2 and 4 failed because we added bad data.

webtestRun

Retrieve the Good Data

If you look in the text file that gets created at C:\DataValidation_OUTPUT, only the iterations that passed will get written to the text file.

DataOutputFile

Now you can simply copy and paste this over your existing data source and know your test will only use good, clean data. It is a good habit to delete the DataValidation.txt file when you are done with it. Otherwise, if you decide to run the web test again with this plug in, data will append to the existing file. Don’t forget to remove this plug-in in your web test once you are complete.

Using XML?

** If you are using an XML file as your data source, the output text file will look like this:

<Row><AccountNumber>73326436</AccountNumber></Row><Row><AccountNumber>xxx</AccountNumber></Row><Row><AccountNumber>15280938</AccountNumber></Row><Row><AccountNumber>xxx</AccountNumber></Row><Row><AccountNumber>15481994</AccountNumber></Row><Row><AccountNumber>15481994</AccountNumber></Row><Row><AccountNumber>15496778</AccountNumber></Row><Row><AccountNumber>73326436</AccountNumber></Row><Row><AccountNumber>xxx</AccountNumber></Row><Row><AccountNumber>15280938</AccountNumber></Row>

You will have to manually wrap the data with root elements so the XML is valid. (Tip: If you paste xml into a XML file in Visual Studio, it will auto format it!)

<Root>
  <Row>
    <AccountNumber>73326436</AccountNumber>
  </Row>
  <Row>
    <AccountNumber>15280938</AccountNumber>
  </Row>
  <Row>
    <AccountNumber>15481994</AccountNumber>
  </Row>
  <Row>
    <AccountNumber>15481994</AccountNumber>
  </Row>
  <Row>
    <AccountNumber>15496778</AccountNumber>
  </Row>
  <Row>
    <AccountNumber>73326436</AccountNumber>
  </Row>
  <Row>
    <AccountNumber>xxx</AccountNumber>
  </Row>
  <Row>
    <AccountNumber>15280938</AccountNumber>
  </Row>
</Root>

If you like to use XML data instead of CSV, here is a great site to convert CSV into XML. There are known issues with Visual Studio when using leading 0s or dates in your CSV data file. Visual Studio may intentionally remove the leading 0s or format your date in an unexpected format. But these issues don’t seem to occur when using XML.

csvToXml

DataValidationWebTestPlugin Plug-in Properties

pluginProperties

Here are the 5 properties that are exposed.

The DataSourceName is the name of your data source. This will help if you have multiple data source files within your web test and need to validate one of them. The plug-in currently does not support multiple data sources. But you can easily do that but adding in looping logic in the code.
DriveLetter, File Name and Folder Name is where you can specify the location of the output file.
Plugin Enabled allows you to turn the plug in ON or OFF.

DataValidationWebTestPlugin Plug-in Code

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.WebTesting;
using System.ComponentModel;
using System.IO;
using System.Xml;

namespace Utilities
{
    public class DataValidationWebTestPlugin : WebTestPlugin
    {
        #region Properties

        [DisplayName("Plugin Enabled")]
        [Description("If set to FALSE, the plugin will skip any execution steps")]
        [DefaultValue(true)]
        public bool Enabled { get; set; }

        [DisplayName("DataSourceName")]
        [DefaultValue("")]
        [Description("Datasource Name of the datasource file.")]
        public string DataSourceName { get; set; }

        [DisplayName("DriveLetter")]
        [DefaultValue("c")]
        [Description("Drive letter of the location to output the text file")]
        public string DriveLetter { get; set; }

        [DisplayName("Folder Name")]
        [DefaultValue("DataValidation_OUTPUT")]
        [Description("Folder name of the location to output the text file. ex. c:\\MyFolder\\")]
        public string FolderName { get; set; }

        [DisplayName("File Name")]
        [DefaultValue("DataValidation")]
        [Description("File Name of the text file")]
        public string FileName { get; set; }

        #endregion

        // empty constructor
        public DataValidationWebTestPlugin() { }

        public override void PostWebTest(object sender, PostWebTestEventArgs e)
        {
            if (Enabled)
            {
                // If the web test passed, then use this plug in to write the data to a text file.
                if (e.WebTest.Outcome == Outcome.Pass)
                {
                    //Create the StreamWriter object to do the writing
                    StreamWriter sw;

                    string fileExtension = ".txt";

                    string path = SetFilePath(DriveLetter, FolderName);
                    string fullFilePathName = path + "\\" + FileName + fileExtension;

                    string headerTextFields = "";

                    Dictionary<string, string> dataDictionary = new Dictionary<string, string>();

                    //Here we are searching for the data source index.
                    int dsIndex = 0;
                    if (!String.IsNullOrEmpty(DataSourceName))
                    {
                        for (int i = 0; i < e.WebTest.DataSources.Count; i++)
                        {
                            var ds = e.WebTest.DataSources[i];
                            if (ds.Name == DataSourceName)
                            {
                                dsIndex = i;
                            }
                        }
                    }

                    string dsName = e.WebTest.DataSources[dsIndex].Name.ToString();
                    string dsTable = e.WebTest.DataSources[dsIndex].Tables[0].Name;
                    string dataSourceType;

                    // Determine if data source is .csv or .xml
                    if (e.WebTest.DataSources[0].Provider.Contains("XML"))
                    {
                        dataSourceType = "XML";
                    }
                    else
                    {
                        dataSourceType = "CSV";
                    }

                    //Get data source fields from context
                    foreach (var dataSourceParameter in e.WebTest.Context)
                    {
                        if (dataSourceParameter.ToString().Contains(dsName))
                        {
                            int paramNameStartIndex;
                            string paramName = "";
                            
                            if (dataSourceType == "XML")
                            {
                                paramNameStartIndex = dataSourceParameter.Key.IndexOf(dsTable);
                                paramName = dataSourceParameter.Key.Substring(paramNameStartIndex + dsTable.Length + 1);                                
                            }
                            else
                            {
                                paramNameStartIndex = dataSourceParameter.Key.ToLower().IndexOf("csv");
                                paramName = dataSourceParameter.Key.Substring(paramNameStartIndex + 4);
                                headerTextFields += paramName + ",";
                            }

                            dataDictionary.Add(paramName, dataSourceParameter.Value.ToString());
                        }
                    }

                    //Remove last comma
                    if (dataSourceType == "CSV")
                        headerTextFields = headerTextFields.Substring(0, headerTextFields.Length - 1);

                    sw = CreateTextFile(fullFilePathName, headerTextFields, dataSourceType);
                    
                    WriteToOutPutFile(sw, dataDictionary, dataSourceType, dsTable);
                         
                    //be sure to close the stream and file or else
                    //the file will be locked
                    sw.Close();
                }
            }
        }

        #region Private Methods

        /// <summary>
        /// This method retuns a list of strings found for the xml element you specify. 
        /// </summary>
        /// <param name="xmlString"></param>
        /// <returns></returns>
        private List<string> GetXMLElementInnerText(string xmlString, string elementToFind)
        {
            List<string> elements = new List<string>();
            XmlDocument doc = new XmlDocument();

            doc.LoadXml(xmlString);

            // Get all the elements
            XmlNodeList foundElementList = doc.GetElementsByTagName(elementToFind.Trim());
            foreach (XmlNode node in foundElementList)
            {
                elements.Add(node.InnerText);
            }

            return elements;
        }

        private StreamWriter CreateTextFile(string fullFilePathName, string headerTextFields, string dataSourceType)
        {
            StreamWriter sw;

            if (!File.Exists(fullFilePathName))
            {
                sw = File.CreateText(fullFilePathName);
                if (dataSourceType == "CSV")
                {
                    sw.WriteLine(headerTextFields);
                }
            }
            else
            {
                sw = File.AppendText(fullFilePathName);
            }

            return sw;
        }

        private string SetFilePath(string driveLetter, string folder)
        {
            // Specify a "currently active folder" 
            string activeDir = driveLetter + @":\";

            //Create a new subfolder under the current active folder 
            string newPath = System.IO.Path.Combine(activeDir, folder);

            // Create the subfolder
            System.IO.Directory.CreateDirectory(newPath);

            return newPath;
        }

        private void WriteToOutPutFile(StreamWriter sw, Dictionary<string, string> rowData, string dataSourceType, string dsTableName)
        {
            if (dataSourceType == "CSV")
            {
                string rowString = "";
                foreach (KeyValuePair<string, string> row in rowData)
                {
                    rowString += row.Value + ",";
                }
                rowString = rowString.Substring(0, rowString.Length - 1);
                sw.WriteLine(rowString);
            }

            if (dataSourceType == "XML")
            {
                XmlDocument xmlDocument = new XmlDocument();
                XmlElement rowElement = xmlDocument.CreateElement(dsTableName);
                xmlDocument.AppendChild(rowElement);

                foreach (KeyValuePair<string, string> row in rowData)
                {
                    XmlElement xmlElement1 = xmlDocument.CreateElement(row.Key);
                    xmlElement1.InnerText = row.Value;
                    rowElement.AppendChild(xmlElement1);
                }

                sw.Write(xmlDocument.OuterXml);

            }
        }

        #endregion
    }
}


Advertisements

1 Comment »

  1. Reblogged this on Sutoprise Avenue, A SutoCom Source.

    Comment by SutoCom — October 31, 2013 @ 6:21 pm | Reply


RSS feed for comments on this post. TrackBack URI

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

Create a free website or blog at WordPress.com.

%d bloggers like this: