This blog post has been updated to reflect changes in VS 2010 RC or later build.  If you are on VS 2010 Beta 2, please upgrade before trying this post. 

This is Part 2 of two part series.  In Part 1 we focused on -

Why is generated code not a straight-line code?

In this part, we will focus on -

How is generated code making reusability & customization easier?

Here we will look into -

  1. Reusability of the test code
  2. Data Driving the Test
  3. Customizing Search Properties
  4. Customizing Complete Method
  5. Complete Control over Code

Reusability of the test code -

By generating code in separate class and methods, the tool encourages reusability.  Say that in addition of the above test, I also want to test the results for Top 3 most popular search phrases. Since my test steps are pretty similar to the above, I should be able to reuse the above code.  I copy the above method and create a new test method by renaming it -

[TestMethod]
public void Top3SearchesTest()
{
    this.UIMap.LaunchBing();
    this.UIMap.SearchBing();
    this.UIMap.VerifyFirstLink();
    this.UIMap.CloseBing();
}

Data Driving the Test -

The next step is to data drive the above test method for the Top 3 searches. I will use Mathew’s blog here to do that. The artificial data for Top 3 searches in CSV format that I have are -

Phrase, FirstLinkText, FirstLink, MinResults
microsoft, Microsoft Corporation, http://www.microsoft.com/, 100000000
wikipedia, Wikipedia, http://www.wikipedia.org/, 1000000
hotmail, Windows Live Hotmail, http://mail.live.com/, 100000000

The code (after some basic formatting) should look like -

[DataSource("Microsoft.VisualStudio.TestTools.DataSource.CSV",
            "|DataDirectory|\\ArtificialTop3BingSearches.csv",
            "ArtificialTop3BingSearches#csv",
            DataAccessMethod.Sequential)]
[DeploymentItem("ArtificialTop3BingSearches.csv")]
[TestMethod]
public void Top3SearchesTest()
{
    this.UIMap.LaunchBing();
    this.UIMap.SearchBing();
    this.UIMap.VerifyFirstLink();
    this.UIMap.CloseBing();
}

At this point, I have added the data source but the columns are still are not bound to the appropriate values. The code should look like -

[DataSource("Microsoft.VisualStudio.TestTools.DataSource.CSV",
            "|DataDirectory|\\ArtificialTop3BingSearches.csv",
            "ArtificialTop3BingSearches#csv",
            DataAccessMethod.Sequential)]
[DeploymentItem("ArtificialTop3BingSearches.csv")]
[TestMethod]
public void Top3SearchesTest()
{
    this.UIMap.SearchBingParams.QEditText =
        TestContext.DataRow["Phrase"].ToString();
    this.UIMap.VerifyFirstLinkExpectedValues.BingHyperlinkInnerText=
        TestContext.DataRow["FirstLinkText"].ToString();
    this.UIMap.VerifyFirstLinkExpectedValues.BingHyperlinkHref =
        TestContext.DataRow["FirstLink"].ToString();

    this.UIMap.LaunchBing();
    this.UIMap.SearchBing();
    this.UIMap.VerifyFirstLink();
    this.UIMap.CloseBing();
}

The 3 new lines here are using the Params classes and doing the data binding.  The first line is binding Phrase column to the text typed in search edit box (which has name ‘q’) and the other two lines are binding the FirstLinkText and FirstLink columns to the InnerText and HREF of the first link respectively.

So using Params, I was able to data drive easily. The bigger benefit is that I can still use SearchBing() method at some other place without data.  This further promotes reusability – if I have a method with parameters, I can data bind any number of these parameters and use recorded (hard-coded) value for the rest.

Customize Search Properties -

The other common task is to configure the search properties for a control.  In the above example, the default search properties of the first hyperlink control is dependent on the search phrase and this will result in search failure. To fix this, we can customize the search property for this using following code in UIMap.cs -

public UIMap()
{
    // Remove data specific properties for the first hyperlink.
    // The remaining ones are still good to get the control.
    HtmlHyperlink firstHyperlink =
        this.BingWindowsInternetEWindow.
        BingBingDocument.Results_containerPane.BingHyperlink;

    firstHyperlink.SearchProperties.Remove(
            HtmlProperties.Hyperlink.InnerText);
    firstHyperlink.FilterProperties.Remove(
            HtmlProperties.Hyperlink.AbsolutePath);
    firstHyperlink.FilterProperties.Remove(
            HtmlProperties.Hyperlink.Href);
    firstHyperlink.FilterProperties.Remove(
            HtmlProperties.Hyperlink.ControlDefinition);
}

Here the fact that UIMap is a partial class has been used.  The code is in UIMap.cs file which is not regenerated by the tool and hence it is safe to edit.  Also, since the code is there in the UIMap constructor, all instances of First Hyperlink will get this change.  If I wanted one particular instance to get it and not all, I could have made similar change inside the Top3SearchesTest method also.

Customize Complete Method -

The last item pending in the above test is to also verify the total result count. The search engines do lot of caching and hence the total results count is not same across the runs.  Hence I will ensure that the total results are more than a minimum count than the exact count.  For this, I will record a VerifyExactResultCount() method & then customize it into VerifyMinimumResultCount() method.

To do this, I go to Top3SearchesTest() method and place the cursor on new line after VerifyFirstLink() call.  I right click here and then left click on “Generate Code for Coded UI Test”->”Use Coded UI Test Builder…” to launch the tool.  I insert the verification on the total results pane’s inner text. The code should look like -

[DataSource("Microsoft.VisualStudio.TestTools.DataSource.CSV",
            "|DataDirectory|\\ArtificialTop3BingSearches.csv",
            "ArtificialTop3BingSearches#csv",
            DataAccessMethod.Sequential)]
[DeploymentItem("ArtificialTop3BingSearches.csv")]
[TestMethod]
public void Top3SearchesTest()
{
    this.UIMap.SearchBingParams.QEditText =
        TestContext.DataRow["Phrase"].ToString();
    this.UIMap.VerifyFirstLinkExpectedValues.BingHyperlinkInnerText=
        TestContext.DataRow["FirstLinkText"].ToString();
    this.UIMap.VerifyFirstLinkExpectedValues.BingHyperlinkHref =
        TestContext.DataRow["FirstLink"].ToString();

    this.UIMap.LaunchBing();
    this.UIMap.SearchBing();
    this.UIMap.VerifyFirstLink();
    this.UIMap.VerifyExactResultCount();
    this.UIMap.CloseBing();
}

Notice the newly added line in bold in the above code. To customize, I go to the UIMap.Designer.cs and manually copy the code related to VerifyExactResultCount() method to UIMap.cs. I then rename the method to VerifyMinimumResultCount() to get the a code like below -

(Yes, the support for customizing a method is bit limited today from user experience side and we will improve upon it in future releases.)

/// <summary>
/// VerifyMinimumResultCount -
/// Use 'VerifyExactResultCountExpectedValues' to pass
/// parameters into this method.
/// </summary>
public void VerifyMinimumResultCount()
{
    #region Variable Declarations
    HtmlSpan totalResultsPane =
        this.BingWindowsInternetEWindow.
        BingBingDocument.TotalResultsPane;
    #endregion

    // Verify that '1-14 of 5,210,000 results' pane's property 'InnerText' equals '1-14 of 5,210,000 results'
    Assert.AreEqual(
        this.VerifyExactResultCountExpectedValues.TotalResultsPaneInnerText,
        totalResultsPane.InnerText);
}

In most cases, there would be need to copy and rename VerifyExactResultCountExpectedValues class and related code too. However, here I will pass an int variable to the method because I do not want this method to ever work on a default value.

After complete customization, the method should look like -

/// <summary>
/// Verifies that the result count is > the min value passed.
/// </summary>
public void VerifyMinimumResultCount(int minResultCount)
{
    HtmlSpan totalResultsPane =
        this.BingWindowsInternetEWindow.
        BingBingDocument.TotalResultsPane;

    // Use regular expression to get the text out of the
    // inner text of TotalResultsPane.
    // The inner text has format like '1-14 of 5,210,000 results'.
    int actualResultCount;
    Match resultPattern = Regex.Match(totalResultsPane.InnerText,
        "[0-9]+-[0-9]+ of ([0-9,]*) results");
    Assert.IsTrue(resultPattern.Success,
        "Regular expression match failed");
    Assert.IsTrue(int.TryParse(resultPattern.Groups[1].Value,
                  NumberStyles.Number,
                  CultureInfo.CurrentCulture,
                  out actualResultCount),
                  "Parsing failed");
    Assert.IsTrue(actualResultCount >= minResultCount,
          "Got less than expected min result");
}

In the above method, I did not use much code from the recorded VerifyExactResultCount() method and could have written this method by hand too. Once this is done, I need to update Top3SearchesTest() method to use VerifyMinimumResultCount().

Complete Control over Code -

All the above steps show that I, as a tester, have the complete control over the test code.  There is no code hidden behind the tool and I can modify\customize any piece of the code. The final step here to customize is to fix closing of the browser even for the failed case. The two way to do that are -

  1. Wrap the code in try\finally block.
  2. Or, move LaunchBing and CloseBing into TestInitialize and TestCleanup methods respectively.

I will use #2 because all my tests have LaunchBing and CloseBing steps as initialize and cleanup steps. The final code for two methods should look like -

[TestMethod]
public void SearchBingTest()
{
    this.UIMap.SearchBing();
    this.UIMap.VerifyFirstLink();
}

[DataSource("Microsoft.VisualStudio.TestTools.DataSource.CSV",
            "|DataDirectory|\\ArtificialTop3BingSearches.csv",
            "ArtificialTop3BingSearches#csv",
            DataAccessMethod.Sequential)]
[DeploymentItem("ArtificialTop3BingSearches.csv")]
[TestMethod]
public void Top3SearchesTest()
{
    this.UIMap.SearchBingParams.QEditText =
        TestContext.DataRow["Phrase"].ToString();
    this.UIMap.VerifyFirstLinkExpectedValues.BingHyperlinkInnerText=
        TestContext.DataRow["FirstLinkText"].ToString();
    this.UIMap.VerifyFirstLinkExpectedValues.BingHyperlinkHref =
        TestContext.DataRow["FirstLink"].ToString();

    this.UIMap.SearchBing();
    this.UIMap.VerifyFirstLink();
    this.UIMap.VerifyMinimumResultCount(
        (int)TestContext.DataRow["MinResults"]);
}

[TestInitialize]
public void Launch()
{
    this.UIMap.LaunchBing();
}

[TestCleanup]
public void Close()
{
    this.UIMap.CloseBing();
}

The final code for the above walkthrough is attached as BingTestFinal.zip.