Coded UI Test中的數據驅動測試

  有關什麼是Coded UI Test以及如何使用Coded UI Test能夠查看個人另外一篇文章:http://www.cnblogs.com/jaxu/p/3706652.htmlhtml

  本文主要介紹如何在Coded UI Test中使用數據驅動測試。考慮這樣一個場景:開發人員提交了一個函數,該函數實現了一個數學公式的運算,經過接收兩個數字並進行數學運算給出結果。測試人員根據給定的數學公式,須要考慮提供各類不一樣狀況的值來循環測試該函數。顯然,咱們須要提供一個數據源,根據數據源中提供的不一樣的值來進行自動化測試。這是最多見的數據驅動測試的案例。在基於Coded UI Test的Webpage自動化測試中,瀏覽器兼容性問題是一般要考慮的,咱們能夠在數據源(數據源多是一個記事本或者一個簡單的Excel表格)中提供要測試的瀏覽器的名稱和版本號,而後讓Coded UI Test自動加載不一樣的瀏覽器來循環測試目標頁面。下面的內容會介紹這些方法。chrome

  這裏有兩篇文章詳細描述瞭如何經過[DataSource]特徵屬性來完成數據驅動Coded UI Test。其基本思想是經過在TestMethod前面添加[DataSource]特徵屬性,並指定數據源的類型和位置,而後Coded UI Test的測試方法在運行時會自動讀取數據源中的數據,在迭代中完成比對。瀏覽器

http://blogs.msdn.com/b/mathew_aniyan/archive/2009/03/17/data-driving-coded-ui-tests.aspx緩存

http://msdn.microsoft.com/en-us/library/ms182527.aspx服務器

  下面這段代碼說明了這一狀況:app

[TestMethod]
[DataSource("System.Data.Odbc", @"Dsn=Excel Files;dbq=C:\Box Office Results.xlsx;defaultdir=C:;driverid=1046;maxbuffersize=2048;pagetimeout=5", "BoxOfficeResults$", DataAccessMethod.Sequential)]
public void MyTest()
{
    int rowIndex = this.TestContext.DataRow.Table.Rows.IndexOf(this.TestContext.DataRow);
    string s = this.TestContext.DataRow[0].ToString();
}

  特徵屬性[DataSource]有多個重載,以用來經過不一樣的方式指定數據源的位置。若是你不知道上述代碼中的數據源鏈接字符串是如何提供的,能夠在Visual Studio中嘗試添加數據源操做,而後拷貝其中自動生成的數據源鏈接字符串。ide

  1. 在Visual Studio中經過VIEW->Other Windows->Data Sources打開數據源窗口
  2. 點擊添加一個新的數據源,選擇Database
  3. 點擊New Connection...在打開的窗口中經過ODBC方式選擇Excel文件,Visual Studio會自動爲你生成鏈接字符串
  4. 拷貝該鏈接字符串

  運行上面的代碼,你會發現其實數據讀取工做是在迭代中完成的。也就是說測試方法會被不斷地迭代,直到數據源中全部行均被讀取完成。數據迭代的方式可經過枚舉DataAccessMethod來指定,一共兩種方式:順序讀取或隨機讀取。函數

  咱們將上面代碼中的Excel文件放到C盤根目錄,而後調試代碼。Excel中的第一行默認會被做爲標題,數據默認會從第二行開始讀取,因此第一次迭代的時候DataRow[0]返回的是Excel中A2單元格的內容。TestContext對象被做爲數據源上下文,經過它你能夠找到數據源的一些屬性。例如經過上面代碼中的第一行獲取到數據源的行索引。這裏是msdn上有關DataRow屬性的說明http://msdn.microsoft.com/en-us/library/microsoft.visualstudio.testtools.unittesting.testcontext.datarow.aspxpost

  將數據源鏈接字符串直接寫在代碼裏並非什麼明智之舉,那有沒有什麼方法能夠將它移到配置文件中呢?答案是確定的!經過msdn的這篇文章咱們能夠得知如何將數據源鏈接字符串移到配置文件中http://msdn.microsoft.com/en-us/library/ms243192.aspx測試

  首先在工程中添加Application Configuration File,即App.config。

  內容以下:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="microsoft.visualstudio.testtools" type="Microsoft.VisualStudio.TestTools.UnitTesting.TestConfigurationSection, Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"/>
  </configSections>

  <connectionStrings>
    <add name="MyExcelConn" providerName="System.Data.Odbc" connectionString="Dsn=Excel Files;dbq=C:\Box Office Results.xlsx;defaultdir=C:;driverid=1046;maxbuffersize=2048;pagetimeout=5"/>
  </connectionStrings>
  <microsoft.visualstudio.testtools>
    <dataSources>
      <add name="BoxOfficeResults" connectionString="MyExcelConn" dataTableName="BoxOfficeResults$" dataAccessMethod="Sequential"/>
    </dataSources>
  </microsoft.visualstudio.testtools>
</configuration>

  注意microsoft.visualstudio.testtools節中type屬性的版本號可能會因爲使用的.NET Framework版本的不一樣有所變化。2.0爲8.0.0.0,3.5爲9.0.0.0,3.5以上應該是10.0.0.0。在App.config文件中添加上述配置信息以後,將測試方法的[DataSource]特徵屬性改爲

[DataSource("BoxOfficeResults")]

  若是Excel文件中存在多個表,則能夠在配置文件的<dataSources>中添加多個<add>節點,指明不一樣的數據表名稱和數據讀取方式。

  還記得文章一開始提到的使用數據驅動測試來實現多瀏覽器兼容性測試嗎?咱們能夠經過下面的代碼來實現。

BrowserWindow.CurrentBrowser = this.TestContext.DataRow[0].ToString();

  將不一樣版本的瀏覽器名稱添加到數據源文件中,能夠是一個簡單的記事本或者.csv文件,如:

BrowserType
IE
firefox
chrome

  注意第一行是標題,[DataSource]特徵屬性在讀取數據時始終會將第一行默認爲標題行,數據默認是從第二行開始讀取的。瀏覽器只須要提供名稱便可,大小寫沒有關係。若是你的測試方法須要在不一樣的瀏覽器中完成測試,則能夠嘗試該方法,但我不保證其中是否會涉及到兼容性問題,就像我在前一篇文章中提到的Coded UI Test如何在頁面上搜索一個控件,若是搜索的條件存在瀏覽器兼容性問題,則可能會拋出異常。

 

  雖然經過[DataSource]特徵屬性能夠很是方便地讀取數據源中的數據來完成數據驅動測試,可是有些狀況下這種方式並不適用。考慮一個簡單的需求:被測試頁面上有一個表格,其數據來源於服務器上的一個Excel文件。經過編寫基於數據驅動測試的Coded UI Test方法來檢查頁面上顯示的內容是否與Excel文件一致。

  因爲[DataSource]特徵屬性是以迭代的方式來進行數據驅動測試的,所以咱們沒法在數據迭代的過程當中去遍歷頁面UI元素。何況,若是迭代中存在Assert斷言,也不太方便咱們輸出測試結果。最終的指望是,遍歷整個UI Table,經過與預先讀取的數據源中的數據進行逐一比對來完成整個測試,其過程多是這樣:

  1. 讀取數據源中的數據並緩存。因爲沒法使用[DataSource]特徵屬性自動讀取數據,所以不得不本身編寫代碼來完成數據讀取操做。
  2. 找到UI Table進行遍歷,將全部單元格的數據緩存。
  3. 將兩部分緩存的數據進行比對,這可能須要預先提供Mapping以幫助完成數據比對過程。

  咱們以http://www.cnblogs.com/jaxu/p/3635634.html頁面中的表格爲例來看看如何實現這一過程。

  下面是UIMap2.uitest中的完整代碼:

public partial class UIMap2
{
    public void LaunchPage()
    {
        this.UIExcelInteractiveViewWindow.LaunchUrl(new Uri("http://www.cnblogs.com/jaxu/p/3635634.html"));
    }

    public void TestTableData()
    {
        HtmlTable targetTable = this.UIExcelInteractiveViewWindow.UIExcelInteractiveViewDocument.UICnblogs_post_bodyPane.UIItemTable;
        HtmlRow rowall = new HtmlRow(targetTable);
        UITestControlCollection rows = rowall.FindMatchingControls();
        Dictionary<int, Dictionary<int, string>> PageTableDataCache = new Dictionary<int, Dictionary<int, string>>();
        int rowCount = rows.Count;

        for (int i = 0; i < rowCount; i++)
        {
            HtmlCell allTD = new HtmlCell(rows[i]);
            UITestControlCollection TDs = allTD.FindMatchingControls();
            Dictionary<int, string> cellsDictionary = new Dictionary<int, string>();

            int tdCount = TDs.Count;
            for (int j = 0; j < tdCount; j++)
            {
                cellsDictionary.Add(j, ((HtmlCell)TDs[j]).InnerText);
            }

            PageTableDataCache.Add(i, cellsDictionary);
        }

        Dictionary<int, Dictionary<int, string>> ExcelDataCache = GetExcelData(ConfigurationManager.AppSettings["ExcelPath"], "BoxOfficeResults");
        // load mapping
        MappingRow[] mappingRows = GetMappingRowCollection();

        string msg = string.Empty;

        for (int i = 0; i < mappingRows.Length; i++)
        {
            Dictionary<int, string> pageRowCellsDictionary = PageTableDataCache[mappingRows[i].PageTableTrIndex];
            Dictionary<int, string> excelRowCellsDictionary = ExcelDataCache[mappingRows[i].ExcelRowNum];
            MappingColumn[] mappingColumns = mappingRows[i].MappingColumn;
            for (int j = 0; j < mappingColumns.Length; j++)
            {
                string excelValue = excelRowCellsDictionary[mappingColumns[j].ExcelColumnNum];
                string pageValue = pageRowCellsDictionary[mappingColumns[j].PageTableTdIndex];

                Assert.AreEqual(excelValue, pageValue, string.Format("Validation failed at row {0} column {1}", mappingRows[i].ExcelRowNum, mappingColumns[j].ExcelColumnNum));
            }
        }
    }

    private Dictionary<int, Dictionary<int, string>> GetExcelData(string filePath, string sheetName)
    {
        Dictionary<int, Dictionary<int, string>> sheetDataDic = new Dictionary<int, Dictionary<int, string>>();
        Application excel = new Application();
        excel.Visible = false;
        excel.UserControl = true;

        try
        {
            Workbook wb = (Workbook)excel.Application.Workbooks.Open(filePath,
                Missing.Value,
                Missing.Value,
                Missing.Value,
                Missing.Value,
                Missing.Value,
                Missing.Value,
                Missing.Value,
                Missing.Value,
                Missing.Value,
                Missing.Value,
                Missing.Value,
                Missing.Value,
                Missing.Value,
                Missing.Value
                );

            foreach (var worksheet in wb.Worksheets)
            {
                Worksheet ws = ((Worksheet)worksheet);
                if (ws.Name.Equals(sheetName))
                {
                    int rowscount = ws.UsedRange.Cells.Rows.Count;
                    int colscount = ws.UsedRange.Cells.Columns.Count;

                    for (int i = 0; i < rowscount; i++)
                    {
                        Dictionary<int, string> cellsDictionary = new Dictionary<int, string>();
                        for (int j = 0; j < colscount; j++)
                        {
                            Range range = ws.Cells.get_Range(ConvertNumberToName(j) + (i + 1));
                            string cellText = ((object)range.Text) == null ? "" : ((object)range.Text).ToString();
                            cellsDictionary.Add(j, cellText);
                        }
                        sheetDataDic.Add(i, cellsDictionary);
                    }
                    break;
                }
            }
        }
        finally
        {
            excel.Application.Workbooks.Close();
            excel.Quit();
            System.Runtime.InteropServices.Marshal.ReleaseComObject(excel);
            excel = null;
            GC.Collect();
        }

        return sheetDataDic;
    }

    private string ConvertNumberToName(int index)
    {
        if (index < 0) { throw new Exception("invalid parameter"); }

        List<string> chars = new List<string>();
        do
        {
            if (chars.Count > 0) index--;
            chars.Insert(0, ((char)(index % 26 + (int)'A')).ToString());
            index = (int)((index - index % 26) / 26);
        } while (index > 0);

        return String.Join(string.Empty, chars.ToArray());
    }

    private MappingRow[] GetMappingRowCollection()
    {
        Mapping mapping = null;
        MappingRow[] mappingRowCollection = null;

        XmlSerializer serializer = new XmlSerializer(typeof(Mapping));

        using (FileStream fs = new FileStream(ConfigurationManager.AppSettings["MappingsPath"], FileMode.Open))
        {
            using (XmlReader reader = XmlReader.Create(fs))
            {
                mapping = (Mapping)serializer.Deserialize(reader);
            }
        }

        if (mapping != null)
        {
            mappingRowCollection = mapping.MappingRow;
        }

        return mappingRowCollection;
    }
}

  其中有兩個public方法:LaunchPage()用來導航到目標頁面;TestTableData()用來遍歷目標頁面上的表格並與數據源Excel中的數據進行比對,給出測試結果。

  私有方法GetExcelData()用來讀取Excel文件中的數據,其中用到了Microsoft.Office.Interop.Excel程序集中的對象,須要在工程中單獨添加引用。數據按行和列的方式存放到Dicitionary字典對象中,字典中的Key爲每一行的行號,Value則是另外一個字典,包含該行全部的列。事實上,程序中的其它地方也用到了這種數據存儲結構。

  在TestTableData()方法中,一共完成了三個步驟:

  1. 首先遍歷頁面上的Table,將單元格的內容緩存到字典對象PageTableDataCache中。該字典對象的數據存儲結構與上面講到的Excel數據存儲結構相同。值得注意的是,在遍歷Table的過程當中因爲是遍歷Table下面全部HtmlRow的集合,此處不包含Header部分,所以Table的表頭部分的數據不會被存儲到字典對象中。
  2. 經過GetExcelData()方法將Excel中的數據緩存到字典對象ExcelDataCache中。
  3. 添加一個Mapping,用來對頁面上的表格和Excel進行映射。爲何須要Mapping?由於頁面上表格中單元格的行和列與Excel中的行和列並不老是一一對應的,因此這裏必需要經過Mapping來進行映射,以肯定如何進行比對。Mapping能夠是一個XML文件,在程序中經過反序列化的方式進行讀取,該操做由私有方法GetMappingRowCollection()完成。下面是Mapping XML文件的內容,有關如何經過Visual Studio自動生成XML反序列化的類,能夠查看這篇文章http://www.cnblogs.com/jaxu/p/3632077.html
    <?xml version="1.0" encoding="utf-8" ?>
    <Mapping>
      <MappingRow ExcelRowNum="1" PageTableTrIndex="0">
        <MappingColumn ExcelColumnNum="0" PageTableTdIndex="0"/>
        <MappingColumn ExcelColumnNum="1" PageTableTdIndex="1"/>
        <MappingColumn ExcelColumnNum="2" PageTableTdIndex="2"/>
        <MappingColumn ExcelColumnNum="3" PageTableTdIndex="3"/>
        <MappingColumn ExcelColumnNum="4" PageTableTdIndex="4"/>
      </MappingRow>
      <MappingRow ExcelRowNum="2" PageTableTrIndex="1">
        <MappingColumn ExcelColumnNum="0" PageTableTdIndex="0"/>
        <MappingColumn ExcelColumnNum="1" PageTableTdIndex="1"/>
        <MappingColumn ExcelColumnNum="2" PageTableTdIndex="2"/>
        <MappingColumn ExcelColumnNum="3" PageTableTdIndex="3"/>
        <MappingColumn ExcelColumnNum="4" PageTableTdIndex="4"/>
      </MappingRow>
      <MappingRow ExcelRowNum="3" PageTableTrIndex="2">
        <MappingColumn ExcelColumnNum="0" PageTableTdIndex="0"/>
        <MappingColumn ExcelColumnNum="1" PageTableTdIndex="1"/>
        <MappingColumn ExcelColumnNum="2" PageTableTdIndex="2"/>
        <MappingColumn ExcelColumnNum="3" PageTableTdIndex="3"/>
        <MappingColumn ExcelColumnNum="4" PageTableTdIndex="4"/>
      </MappingRow>
      <MappingRow ExcelRowNum="4" PageTableTrIndex="3">
        <MappingColumn ExcelColumnNum="0" PageTableTdIndex="0"/>
        <MappingColumn ExcelColumnNum="1" PageTableTdIndex="1"/>
        <MappingColumn ExcelColumnNum="2" PageTableTdIndex="2"/>
        <MappingColumn ExcelColumnNum="3" PageTableTdIndex="3"/>
        <MappingColumn ExcelColumnNum="4" PageTableTdIndex="4"/>
      </MappingRow>
      <MappingRow ExcelRowNum="5" PageTableTrIndex="4">
        <MappingColumn ExcelColumnNum="0" PageTableTdIndex="0"/>
        <MappingColumn ExcelColumnNum="1" PageTableTdIndex="1"/>
        <MappingColumn ExcelColumnNum="2" PageTableTdIndex="2"/>
        <MappingColumn ExcelColumnNum="3" PageTableTdIndex="3"/>
        <MappingColumn ExcelColumnNum="4" PageTableTdIndex="4"/>
      </MappingRow>
      <MappingRow ExcelRowNum="6" PageTableTrIndex="5">
        <MappingColumn ExcelColumnNum="0" PageTableTdIndex="0"/>
        <MappingColumn ExcelColumnNum="1" PageTableTdIndex="1"/>
        <MappingColumn ExcelColumnNum="2" PageTableTdIndex="2"/>
        <MappingColumn ExcelColumnNum="3" PageTableTdIndex="3"/>
        <MappingColumn ExcelColumnNum="4" PageTableTdIndex="4"/>
      </MappingRow>
      <MappingRow ExcelRowNum="7" PageTableTrIndex="6">
        <MappingColumn ExcelColumnNum="0" PageTableTdIndex="0"/>
        <MappingColumn ExcelColumnNum="1" PageTableTdIndex="1"/>
        <MappingColumn ExcelColumnNum="2" PageTableTdIndex="2"/>
        <MappingColumn ExcelColumnNum="3" PageTableTdIndex="3"/>
        <MappingColumn ExcelColumnNum="4" PageTableTdIndex="4"/>
      </MappingRow>
      <MappingRow ExcelRowNum="8" PageTableTrIndex="7">
        <MappingColumn ExcelColumnNum="0" PageTableTdIndex="0"/>
        <MappingColumn ExcelColumnNum="1" PageTableTdIndex="1"/>
        <MappingColumn ExcelColumnNum="2" PageTableTdIndex="2"/>
        <MappingColumn ExcelColumnNum="3" PageTableTdIndex="3"/>
        <MappingColumn ExcelColumnNum="4" PageTableTdIndex="4"/>
      </MappingRow>
      <MappingRow ExcelRowNum="9" PageTableTrIndex="8">
        <MappingColumn ExcelColumnNum="0" PageTableTdIndex="0"/>
        <MappingColumn ExcelColumnNum="1" PageTableTdIndex="1"/>
        <MappingColumn ExcelColumnNum="2" PageTableTdIndex="2"/>
        <MappingColumn ExcelColumnNum="3" PageTableTdIndex="3"/>
        <MappingColumn ExcelColumnNum="4" PageTableTdIndex="4"/>
      </MappingRow>
      <MappingRow ExcelRowNum="10" PageTableTrIndex="9">
        <MappingColumn ExcelColumnNum="0" PageTableTdIndex="0"/>
        <MappingColumn ExcelColumnNum="1" PageTableTdIndex="1"/>
        <MappingColumn ExcelColumnNum="2" PageTableTdIndex="2"/>
        <MappingColumn ExcelColumnNum="3" PageTableTdIndex="3"/>
        <MappingColumn ExcelColumnNum="4" PageTableTdIndex="4"/>
      </MappingRow>
    </Mapping>

    同時,因爲程序中須要讀取Excel文件以及反序列化Mapping XML文件,須要在App.config文件中添加兩個配置項。

    <appSettings>
      <add key="MappingsPath" value="..\..\..\CodedUITestProject2\Mapping.xml"/>
      <add key="ExcelPath" value="c:\Box Office Results.xlsx"/>
    </appSettings>

    注意MappingPath的路徑使用的是相對路徑,經過將該文件設置爲拷貝到輸出路徑以方便部署。方法是在Visual Studio的Solution Explorer中右鍵選擇該文件->Properties,將Copy to Output Directory改爲Copy always。

  4. 遍歷Mapping中全部行和列,並比較PageTable和Excel中的數據,經過Assert斷言來獲取測試結果。因爲Assert斷言在測試失敗時總會拋出異常而終止餘下的測試步驟,能夠參考文章http://www.cnblogs.com/jaxu/p/3706652.html中有關Assert斷言部分對上述代碼進行改進。
  5. 添加測試方法以調用LaunchPage()和TestTableData()
    [TestMethod]
    public void MyTest()
    {
        UIMap2 uimap = new UIMap2();
        uimap.LaunchPage();
        uimap.TestTableData();
    }

  下面的代碼由Visual Studio自動生成並作了少許修改(將byte類型改成int),用來將Mapping XML進行反序列化。

/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class Mapping
{

    private MappingRow[] mappingRowField;

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute("MappingRow")]
    public MappingRow[] MappingRow
    {
        get
        {
            return this.mappingRowField;
        }
        set
        {
            this.mappingRowField = value;
        }
    }
}

/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class MappingRow
{

    private MappingColumn[] mappingColumnField;

    private int excelRowNumField;

    private int pageTableTrIndexField;

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute("MappingColumn")]
    public MappingColumn[] MappingColumn
    {
        get
        {
            return this.mappingColumnField;
        }
        set
        {
            this.mappingColumnField = value;
        }
    }

    /// <remarks/>
    [System.Xml.Serialization.XmlAttributeAttribute()]
    public int ExcelRowNum
    {
        get
        {
            return this.excelRowNumField;
        }
        set
        {
            this.excelRowNumField = value;
        }
    }

    /// <remarks/>
    [System.Xml.Serialization.XmlAttributeAttribute()]
    public int PageTableTrIndex
    {
        get
        {
            return this.pageTableTrIndexField;
        }
        set
        {
            this.pageTableTrIndexField = value;
        }
    }
}

/// <remarks/>
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class MappingColumn
{

    private int excelColumnNumField;

    private int pageTableTdIndexField;

    /// <remarks/>
    [System.Xml.Serialization.XmlAttributeAttribute()]
    public int ExcelColumnNum
    {
        get
        {
            return this.excelColumnNumField;
        }
        set
        {
            this.excelColumnNumField = value;
        }
    }

    /// <remarks/>
    [System.Xml.Serialization.XmlAttributeAttribute()]
    public int PageTableTdIndex
    {
        get
        {
            return this.pageTableTdIndexField;
        }
        set
        {
            this.pageTableTdIndexField = value;
        }
    }
}

  UIMap2.uitest中的代碼實現了Excel數據與頁面Table中的內容比對,並最終經過了測試。不過從嚴格意義上來說,這種狀況應該不屬於數據驅動測試的範疇,數據驅動測試的意義在於經過給定的數據來測試程序已有的功能,而上述狀況是經過數據源來比對UI中的內容。不過咱們仍然能夠從中瞭解到如何編寫代碼來讀取數據源並對Webpage中的表格進行Coded UI test。

相關文章
相關標籤/搜索