表数据源

在继续本部分之前,请确保熟悉 TAEF 的基本执行并知道如何使用它 创作测试

编写并处理 TAEF 的基本测试自动化后,可以专注于同一测试代码可用于处理不同数据集的方案。 为此,TAEF 为数据驱动测试提供了“基于表”的方法。 让我们看一个简单的示例,了解如何创作数据驱动测试。

请考虑一个简单的非数据驱动示例,在该示例中,你要将大小和主题打印到控制台。 在本练习中,你将此测试转换为数据驱动测试。

1  namespace WEX { namespace TestExecution { namespace Examples
2  {
3     void DataDrivenTests::FirstTable()
4     {
5         int size = 12;
6         Log::Comment(String().Format(L"Size retrieved was %d", size));
7     }
8
9     void DataDrivenTests::SecondTable()
10    {
11        String theme = "Aero";
12        Log::Comment(L"Theme supplied as " + theme);
13    }
14 } /* namespace Examples */ } /* namespace TestExecution */ } /* namespace WEX */

定义数据

现在,你希望上述函数适用于一组大小和主题。 换句话说,需要函数可以使用的变体数据值。 为此,请在 XML 文件中定义两个表,DataDrivenTests.xml :

1  <?xml version="1.0"?>
2  <Data>
3  <Table Id ="Table1">
4          <ParameterTypes>
5                  <ParameterType Name="Size">Int32</ParameterType>
6                  <ParameterType Name="Color">String</ParameterType>
7                  <ParameterType Name="Transparency">Boolean</ParameterType>
8          </ParameterTypes>
9          <Row Priority="1" Owner="C2">
10                 <Parameter Name="Size">12</Parameter>
11                 <Parameter Name="Color">Blue</Parameter>
12                 <Parameter Name="Transparency">True</Parameter>
13         </Row>
14         <Row Priority="2" Owner="wex">
15                 <Parameter Name="Size">4</Parameter>
16                 <Parameter Name="Color">White</Parameter>
17                 <Parameter Name="Transparency">False</Parameter>
18         </Row>
19         <Row Owner="C2">
20                 <Parameter Name="Size">9</Parameter>
21                 <Parameter Name="Color">Black</Parameter>
22                 <Parameter Name="Transparency">True</Parameter>
23         </Row>
24 </Table>
25 <Table id ="Table2">
26         <Row Description="ButtonTest" Owner="C2" Priority="1">
27                 <Parameter Name="Control">Button</Parameter>
28                 <Parameter Name="Theme">Aero</Parameter>
29         </Row>
30         <Row Description="ComboBoxTest" Priority="2">
31                 <Parameter Name="Control">ComboBox</Parameter>
32                 <Parameter Name="Theme">Classic</Parameter>
33         </Row>
34         <Row Description="ListviewTest" Owner="wex">
35                 <Parameter Name="Control">Listview</Parameter>
36                 <Parameter Name="Theme">AeroBasic</Parameter>
37         </Row>
38 </Table>
39 </Data>

现已定义两个表,“Table1”和“Table2”。 可以在同一 XML 文件中为多个测试方法定义表。

请注意,在 Table1 中,你预先定义了 ParameterTypes,并选择了“Size”作为整数。 ParameterTypes 部分是可选的。 默认情况下,如果未提供参数类型信息,则会将其保存为字符串。 “Table2”中的所有参数都是这种情况。

表中定义的每个“行”都是一组数据 (参数) 希望测试函数接受的值。 第 9、14 和 19 行定义了 FirstTable 函数将接受的 3 组数据。 同样,第 26、30 和 34 行定义 SecondTable 的数据集。

请注意上面的示例中第 9、14、19、26、30 和 34 行 - 可以定义特定于行的元数据。 现在有一种方法让元数据信息与同一函数的数据集一起更改。 第 9 行) (第一组数据的优先级为 1,第二组数据 (第 14 行) 优先级为 2,第三组数据 (第 19 行) 默认为函数的优先级。 所有行都从与表关联的函数继承元数据。 如果在行级别再次指定相同的元数据,它将替代在函数级别定义的元数据值。

注意:本机代码和托管代码的 XML 文件架构定义相同,但支持的类型定义除外。 有关如何定义数据的另一个示例,请参阅下面的“托管数据驱动测试”部分的初始部分。 继续执行本机数据驱动测试,了解本机代码中允许的类型。

本机数据驱动测试

定义数据集并准备好使用后,现在需要一种方法将测试函数限定为数据驱动测试,并将其与定义数据集的表相关联。 这是在创作测试时通过额外的元数据完成的:

1  namespace WEX { namespace TestExecution { namespace Examples
2  {
3      class DataDrivenTests
4      {
5          TEST_CLASS(DataDrivenTests);
6
7          BEGIN_TEST_METHOD(SecondTable)
8              TEST_METHOD_PROPERTY(L"DataSource", L"Table:DataDrivenTests.xml#Table2")
9              TEST_METHOD_PROPERTY(L"Priority", L"3")
10         END_TEST_METHOD()
11
12         BEGIN_TEST_METHOD(FirstTable)
13             TEST_METHOD_PROPERTY(L"Priority", L"4")
14             TEST_METHOD_PROPERTY(L"DataSource", L"Table:DataDrivenTests.xml#Table1")
15         END_TEST_METHOD()
16     };
17 } /* namespace Examples */ } /* namespace TestExecution */ } /* namespace WEX */

若要将 XML 表与测试相关联,请将“DataSource”元数据添加到测试的 方法。 通过此关联,TAEF 将使用给定的数据源来驱动测试。 DataSource 值包含三个部分:

  1. “Table:”- 这会将数据源标识为 XML 表。
  2. “DataDrivenTests.xml”- 这是包含 XML 表的文件。
  3. “#Table2” - 在“#”定义符之后,“Table2”值标识 XML 文档中要使用的特定表。 单个 XML 表数据源可以包含多个表。 TAEF 将在 XML 文件中查找具有与指定值匹配的“Id”属性的 Table 元素。

你可能在上面的示例中观察到,“SecondTable”是在“FirstTable”之前定义的。 这意味着“SecondTable”函数将在“FirstTable”函数之前执行,但你定义了“Table1”,即对应于“FirstTable”的表,在“Table2”之前,即对应于“SecondTable”的表。 这是为了强调 表定义的顺序在发现和执行数据驱动测试期间无关紧要。

完成数据源到测试方法的映射后,现在可以修改示例以从源获取数据。 在执行此操作之前,请查看已发布的头文件 TestData.h。 感兴趣的部分是:

1    class TestData
2    {
3    public:
4        template <typename T>
5        static HRESULT __stdcall TryGetValue(_In_z_ const wchar_t* pszString, T& result)
6        {
7            return Private::TestData<T>::TryGetValue(pszString, result);
8        }
9    };

第 5 行显示要调用以检索函数中的数据的 API。 查看可用于检索 的参数类型

确定 - 全部设置为重新编写示例:

1  namespace WEX { namespace TestExecution { namespace Examples
2  {
3      void DataDrivenTests::FirstTable()
4      {
5          Log::Comment(L"I am in first table");
6          int size;
7          if (SUCCEEDED(TestData::TryGetValue(L"size", size)))
8          {
9              VERIFY_ARE_NOT_EQUAL(size, 0);
10             Log::Comment(String().Format(L"Size retrieved was %d", size));
11         }
12     }
13
14     void DataDrivenTests::SecondTable()
15     {
16         Log::Comment(L"I am in second table.");
17         String theme;
18         if (SUCCEEDED(TestData::TryGetValue(L"theme", theme)))
19         {
20             Log::Comment(L"Theme supplied as " + theme);
21         }
22     }
23 } /* namespace Examples */ } /* namespace TestExecution */ } /* namespace WEX */

第 7 行和第 18 行是为了使测试数据驱动而更改main部分。 变化不大。 查看执行数据驱动测试,了解如何在执行数据驱动测试时充分利用 TAEF。

托管数据驱动测试

假设有一个示例,其中你想要在主机上打印矩形的坐标。 首先将这些坐标定义为 XML 文件中的数据集。

1  <?xml version="1.0"?>
2  <Data>
3  <Table Id="FirstTable">
4          <ParameterTypes>
5                  <ParameterType Name="Left">Int32</ParameterType>
6                  <ParameterType Name="Right">String</ParameterType>
7                  <ParameterType Name="Top">Integer</ParameterType>
8                  <ParameterType Name="Bottom">Int32</ParameterType>
9          </ParameterTypes>
10         <Row Priority="1" Owner="C2" Description="Zero rect">
11                 <Parameter Name="Left">0</Parameter>
12                 <Parameter Name="Right">0</Parameter>
13                 <Parameter Name="Top">0</Parameter>
14                 <Parameter Name="Bottom">0</Parameter>
15         </Row>
16         <Row Priority="2" Owner="wex" Description="normal rect">
17                 <Parameter Name="Left">12</Parameter>
18                 <Parameter Name="Right">25</Parameter>
19                 <Parameter Name="Top">10</Parameter>
20                 <Parameter Name="Bottom">50</Parameter>
21         </Row>
22         <Row Owner="C2" Description="invalid rect">
23                 <Parameter Name="Left">30</Parameter>
24                 <Parameter Name="Right">15</Parameter>
25                 <Parameter Name="Top">40</Parameter>
26                 <Parameter Name="Bottom">10</Parameter>
27         </Row>
28 </Table>
29 </Data>

在表的范围内定义数据集,在本例中为“FirstTable”,这是在上面的第 3 行中定义的。 可以在同一 XML 文件中为多个测试方法定义表。

观察 FirstTable 预先定义 ParameterTypes,并将“Left”称为“Int32”。 ParameterTypes 部分是可选的。 默认情况下,如果未提供参数类型信息,它将保存为字符串。

查看支持 的参数类型列表。

如果指定了任何其他数据类型,则测试将引发警告,并将其视为字符串。

注意:类型字符串不区分大小写,但应完全如上所示拼写。

表中定义的每个“行”都是一组数据 (参数) 希望测试函数接受的值。 第 10、16 和 22 行定义了函数的 3 组数据。

请注意上述示例中的第 10、16 和 22 行 - 可以定义特定于 Row 的元数据。 现在,可以使用同一函数的数据集更改元数据信息。 第一组数据 (第 10 行) 的优先级为 1,第二组数据 (第 16 行) 优先级为 2,第 22 行) 第三组数据 (优先级默认为函数的优先级。 所有行都从与表关联的函数继承元数据。 如果在行级别再次指定相同的元数据,它将替代在函数级别定义的元数据值。

注意:本机代码和托管代码的 XML 文件架构定义相同,但支持的类型定义除外。 查看本页顶部的“定义数据”部分,获取另一个如何定义此数据的示例。

现在,你已定义所有数据。 以下示例演示如何访问它。

1  namespace WEX.Examples
2  {
3      using Microsoft.VisualStudio.TestTools.UnitTesting;
4      using System;
5      using System.Collections;
6      using WEX.Logging.Interop;
7      using WEX.TestExecution;
8
9      [TestClass]
10     public class CSharpDataDrivenTests
11     {
12         [TestMethod]
15         [DataSource("Table:CSharpDataDrivenTests.xml#FirstTable")]
16         public void First()
17         {
18             Console.WriteLine("Left is " + m_testContext.DataRow["Left"].ToString());
19
20             Log.Comment("In CSharpDataDrivenTests.First");
21         }
22
23         [TestMethod]
24         public void Second()
25         {
26             Log.Comment("In CSharpDataDrivenTests.Second");
27             Verify.IsTrue(true);
28         }
29
30         public TestContext TestContext
31         {
32             get { return m_testContext; }
33             set { m_testContext = value; }
34         }
35
36         private TestContext m_testContext;
37     }
38 }

将 XML 表与托管代码中的给定测试方法相关联与本机代码非常相似;只需应用“DataSource”元数据。 与以前一样,它由三个部分组成:

  1. “Table:”- 将数据源标识为 XML 表。
  2. “CSharpDataDrivenTests.xml”- 包含 XML 表的文件。
  3. “#FirstTable” - 在“#”定义符之后,“FirstTable”值标识 XML 文档中要使用的特定表。 TAEF 将在 XML 文件中查找具有与指定值匹配的“Id”属性的 Table 元素。

请注意,Second 函数不是数据驱动的。 可以选择只让部分测试由数据驱动。 还可以选择让每个测试在不同的 XML 文件中定义其表。

在第 36 行中,定义专用 TestContext 属性 , 就像 VSTS 建议 TestContext 类一样。 还可以 (行 30 到 34) 为此属性定义公共评估员。 在内部,TAEF 加载 TestContext 的字典属性,并将相应的数据集放在焦点中。

TestContext 在 Microsoft.VisualStudio.TestTools.UnitTesting 中定义。 请参阅上例中的第 3 行。 应该已在托管测试创作中将其作为引用包含在内。 因此,创作数据驱动测试不需要其他引用。

在上述示例的第 18 行中,展示了如何在 函数中检索数据。 请注意,数据在 m_testContext.DataRow 中可用。

用于标识 DataRow 的名称而不是索引

TAEF 允许使用更有意义的“Name”属性而不是 Index 来标识数据源中的任何 DataRow。 为此,只需在 DataSource 中的“行”级别添加“名称”元数据。 可以修改此页上的第一个示例以使用此功能,如下所示:

1  <?xml version="1.0"?>
2  <Data>
3  <Table id ="Table1">
4          <ParameterTypes>
5                  <ParameterType Name="Size">Int32</ParameterType>
6                  <ParameterType Name="Color">String</ParameterType>
7                  <ParameterType Name="Transparency">Boolean</ParameterType>
8          </ParameterTypes>
9          <Row Name='BlueTransparent' Priority="1" Owner="C2">
10                 <Parameter Name="Size">12</Parameter>
11                 <Parameter Name="Color">Blue</Parameter>
12                 <Parameter Name="Transparency">True</Parameter>
13         </Row>
14         <Row Priority="2" Owner="wex">
15                 <Parameter Name="Size">4</Parameter>
16                 <Parameter Name="Color">White</Parameter>
17                 <Parameter Name="Transparency">False</Parameter>
18         </Row>
19         <Row Name='BlackTransparent' Owner="C2">
20                 <Parameter Name="Size">9</Parameter>
21                 <Parameter Name="Color">Black</Parameter>
22                 <Parameter Name="Transparency">True</Parameter>
23         </Row>
24 </Table>
25 ...
39 </Data>

在上述修改的示例中,“BlueTransparent”对应于索引 0。 索引为 1 的行没有为其指定特殊名称,索引为 2 的行具有关联的名称“BlackTransparent”。 你仍然可以使用选择查询在“Table1”中查找索引 0 或 2,它将找到正确的行。 但是,在执行或列出 dll 时,而不是看到:

<qualified name of the test method>#<index>

你会看到:

<qualified name of the test method>#<name property provided at Row level>

,用于在行级别提供“名称”属性的行。 如果未为任何 Row 提供“Name”属性(如上面的索引 1 的情况),则该方法的限定名称将默认为 #<index>

请注意,通过在行级别提供“名称”属性,实质上是更改 TAEF 使用相应的 Row 数据解释方法调用实例名称的方式。

DataSource 作为运行时参数

TAEF 支持将数据源作为运行时参数提供。 此配置的语法如下:

te <test dll names> /p:<DataSource runtime name>=Table:<DataSoure XML file>#<Table Id>

在创作相关测试时,必须指定“p:<DataSource 运行时名称>”作为数据源。 请记住,必须在运行时指定完整的字符串(XML 文件名以及表 ID)。 如果在运行时提供数据源,则不应将 TableId 作为测试元数据提供。 “Table:”前缀指定要查找表数据源。

可以使用发布共享上提供的示例之一来尝试此操作:

te Examples\CPP.RuntimeDataSource.Example.dll /p:MyDataSource=Table:RuntimeDataSourceExample.xml#SimpleTable

DataSource 即资源

TAEF 允许将数据源添加为测试模块的资源,前提是它符合以下条件:

对于本机测试模块,可以通过将 DataSource 指定为资源 ID 或资源名称来执行此操作。 下面是代码示例:

BEGIN_TEST_METHOD(ResourceNameDataSource)
    TEST_METHOD_PROPERTY(L"DataSource", L"Table:MyResourceName#SimpleTable")
END_TEST_METHOD()

在本例中,“MyResourceName”是 ResourceDataSource.rc 文件中定义的资源名称:

MyResourceName DATASOURCE_XML "ResourceDataSource.xml"

对于托管测试模块,只能按特定方式指定资源,如下面所示的 源文件 代码片段中所示:

LANGUAGE_NEUTRAL_MANAGED_RESOURCES = CSharpAdvancedDataDrivenTests.xml

DataSource 元数据规范将保持与指定 DataSource XML 文件时的相同。 与托管代码中的情况类似,可以使资源名称与 XML 文件名相同。 因此,必须了解 TAEF 将首先查找是否存在具有 DataSource 名称的实际文件。 如果未找到此类 XML 文件,则只有这样,它才能继续在测试模块中查找具有给定资源名称或 ID 的测试资源。由于将 DataSource 指定为资源需要重新编译,因此可以通过在开发 (并将资源名称命名为与 xml 文件名相同的) 时,将 DataSource XML 文件复制到与测试 dll 相同的位置来利用此设计。 完成测试后,将 XML 复制回代码目录,并重新编译为资源。 不要忘记从执行目录中删除 XML 文件! :)

示例演练

若要掌握基于表的数据驱动测试的各个方面,请阅读更多示例演练: