WMI 数据源

在继续执行本部分之前,请确保熟悉 TAEF 的基本执行,并了解如何使用 TAEF 编写测试。

背景

“WMI”代表“Windows Management Instrumentation”。 使用通用信息模型(CIM),这是表示系统的行业标准。 Windows Management Instrumentation 提供了访问系统管理信息的统一方法。

它如何帮助我的测试?

使用在 TAEF 中作为 WMI 数据源提供的 WMI 查询支持,可以在测试中添加前置条件,并在运行测试之前获取有关测试计算机上的资源的信息。 下面是可以使用 WMI 进行查询类型的一些示例:

  • 检查测试正在运行的计算机是否是笔记本电脑,并且仅当它是笔记本电脑时运行测试。
  • 检查是否已在测试计算机上安装了服务包,并仅在安装后运行测试。
  • 检索测试计算机上的所有可移动驱动器和本地硬盘驱动器,并针对与查询匹配的每个驱动器运行测试。
  • 仅在测试计算机未加入域的情况下运行测试
  • 仅在测试计算机已加入域的情况下运行测试,并检索域名。

这有望让你了解在何处以及如何利用 WMI DataSource 进行测试。 让我们看看在创作 TAEF 测试时如何添加此 WMI 查询支持。

要使测试成为 WMI 数据源测试,您唯一需要的特殊元数据是“DataSource”。 DataSource 语法必须如下所示:

[DataSource("WMI:<WQL query>")]

或在原生代码中:

TEST_METHOD_PROPERTY(L"DataSource", L"WMI:<WQL query>")]

必须注意到,DataSource 值以“WMI:”开头,这让 TAEF 知道这确实是依赖于 WMI 查询结果的测试的数据源,还将其与数据驱动测试区分开来。 当前 TAEF 不支持同时进行的数据驱动测试和依赖 WMI 查询结果的测试,这正是一个很好的机会来提及这一点。

下一个问题自然是如何编写 WQL 查询来查找所需的信息? WQL 查询语法与简化的 SQL 查询非常相似。 在 WMI 任务适用于脚本和应用程序 中提供了一些非常好的查询示例。 以下是一些示例:

SELECT Description、DesktopInteract、ProcessId FROM Win32_Service WHERE Name='Themes'
在发现要用于测试的 Description、DesktopInteract 和 ProcessId 属性后,在“主题”服务上运行测试。

SELECT 能力、功能描述 FROM Win32_Printe
请为连接到此计算机的每台打印机运行测试。 允许测试访问每个打印机的功能及其功能描述。

SELECT 名称、用户、位置 FROM Win32_StartupCommand
针对在 Windows 启动时运行的每个进程运行测试。 对于每个进程,让测试知道进程的名称是什么、所在位置,以及以什么用户身份运行。

可以在上面提到的文档中以及打开的示例中的.cs文件和头文件中找到更多示例。 一般过度简化的语法如下所示:

SELECT <comma separated properties> FROM <WMI Class name> [WHERE <add condition on some properties>]

在刚刚看到的示例中,Win32_Service、Win32_Printer和Win32_StartupCommand都是 WMI 类。 可以在 WMI 类中查找 WMI 类

TAEF 不支持检索系统属性。

在后台 TAEF 将为你执行查询并确认结果。 如果至少有一个对象作为查询结果返回,则会为每个返回的对象执行测试。 如果 WQL 查询未返回任何对象,则测试将记录为“阻止”,并且执行将转到下一个测试。

在创作测试之前检查或验证查询似乎是一个好主意,并且是一个非常简单的过程:

  • 从运行对话框或命令提示符调用“wbemtest.exe”
  • 单击右上角的“连接”按钮。
  • 在右上角再次单击“连接”之前,请确保命名空间为“root\cimv2”。
  • 在“IWbemServices”下,单击“查询”
  • 在显示的编辑框中输入查询,然后单击“应用”

注意:“IWbemService”还有其他几个选项,可以帮助你进行查询。 例如,使用“枚举类”并将单选按钮更改为“递归”选项,这将帮助你查看系统中的所有WMI类。

从 WMI 查询中检索属性

现在,你已了解了如何为测试方法提出 WMI 查询,以及如何在创作测试时将其作为元数据应用。 你还知道如何使用 wbemtest.exe确认查询是否有效。 现在,让我们看看如何检索要查找的属性值。

检索此信息的基本方法与检索数据驱动测试的值相似程度很高。 例如,在托管代码中,如下所示:

1 namespace WEX.Examples
2 {
3     using Microsoft.VisualStudio.TestTools.UnitTesting;
4     using System;
5     using System.Collections;
6     using System.Data;
7     using WEX.Logging.Interop;
8     using WEX.TestExecution;
9
10    [TestClass]
11    public class CSharpWmiDataSourceExample
12    {
13        [TestMethod]
14        [DataSource("WMI:SELECT Description, DesktopInteract, ProcessId FROM Win32_Service WHERE Name='Themes'")]
15        public void ThemesTest()
16        {
17            String description = (String)m_testContext.DataRow["Description"];
18            Boolean desktopInteract = (Boolean)m_testContext.DataRow["DesktopInteract"];
19            UInt32 processId = (UInt32)m_testContext.DataRow["ProcessId"];
20            Log.Comment("Themes service is running on process " + processId.ToString() + " with desktop interact set to "
                           + desktopInteract.ToString());
21            Log.Comment("Themes service description: " + description);
22        }
23        ...
24        public TestContext TestContext
25        {
26            get { return m_testContext; }
27            set { m_testContext = value; }
28        }
29
30        private TestContext m_testContext;
31    }
32}

以上示例中的第 24-30 行正是托管数据驱动测试所需的内容。 你定义了一个专用 TestContext 属性,并为其提供了公共 getter 和 setter,以便 TAEF 设置正确的值。 使用专用 TestContext 属性,可以为从 TAEF 检索到的任何 WMI 查询结果对象的属性检索当前值。

用于检索 WMI 属性的本机代码非常相似。 与本机数据驱动测试一样,你将使用 TestData 获取属性值。 例如,让我们考虑一下获取默认打印机的属性的测试。 头文件实现此测试如下所示:

1        // Test on the default printer and its driver name
2        BEGIN_TEST_METHOD(DefaultPrinterTest)
3            TEST_METHOD_PROPERTY(L"DataSource",
              L"WMI:SELECT DriverName, DeviceId, LanguagesSupported FROM Win32_Printer WHERE Default = True")
4        END_TEST_METHOD()

为此,cpp 文件中的检索代码如下所示:

1     void WmiExample::DefaultPrinterTest()
2     {
3         String deviceId;
4         VERIFY_SUCCEEDED(TestData::TryGetValue(L"DeviceId", deviceId));
5
6         String driverName;
7         VERIFY_SUCCEEDED(TestData::TryGetValue(L"DriverName", driverName));
8
9         TestDataArray<unsigned int> languagesSupported;
10        VERIFY_SUCCEEDED(TestData::TryGetValue(L"LanguagesSupported", languagesSupported));
11
12        Log::Comment(L"The default driver is " + deviceId + L" which is a " + driverName);
13        size_t count = languagesSupported.GetSize();
14        for (size_t i = 0; i < count; i++)
15        {
16            Log::Comment(String().Format(L"Language supported: %d", languagesSupported[i]));
17        }
18    }

考虑可能的 NULL 属性值

需要记住的是,WMI 查询不一定总是会返回非空属性。 有时返回的 WMI 属性值为“null”。 如果认为某些情况下要查找的属性可能为“null”,请在验证或尝试使用它之前检查它。

例如,在托管测试代码中,TestContext 会将 null 值存储为 DBNull 类型的对象。 在尝试将结果的值强制转换为预期类型之前,必须检查对象是否为 DBNull 类型。 让我们来实际操作一下:

1 namespace WEX.Examples
2 {
3     using Microsoft.VisualStudio.TestTools.UnitTesting;
4     using System;
5     using System.Collections;
6     using System.Data;
7     using WEX.Logging.Interop;
8     using WEX.TestExecution;
9
10    [TestClass]
11    public class CSharpWmiDataSourceExample
12    {
13        [TestMethod]
14        [DataSource("WMI:SELECT MaximumComponentLength, Availability, DeviceId, DriveType, Compressed
                         FROM Win32_LogicalDisk WHERE DriveType=2 Or DriveType=3")]
15        public void LogicalDiskTest()
16        {
17            UInt32 driveType = (UInt32)m_testContext.DataRow["DriveType"];
18            Log.Comment("DeviceId is " + m_testContext.DataRow["DeviceId"]);
19            Log.Comment("DriveType is " + driveType.ToString());
20
21            object nullCheckCompressed = m_testContext.DataRow["Compressed"];
22            Log.Comment("Compressed's type is: " + nullCheckCompressed.GetType().ToString());
23            if (nullCheckCompressed.GetType() == typeof(DBNull))
24            {
25                Log.Comment("Compressed is NULL");
26            }
27            else
28            {
29                Boolean compressed = (Boolean)nullCheckCompressed;
30                Log.Comment("Compressed is " + compressed.ToString());
31            }
32
33            object nullCheckMaxComponentLength = m_testContext.DataRow["MaximumComponentLength"];
34            if (nullCheckMaxComponentLength.GetType() == typeof(DBNull))
35            {
36                Log.Comment("MaxComponentLength is NULL");
37            }
38            else
39            {
40                UInt32 maxComponentLength = (UInt32)nullCheckMaxComponentLength;
41                Log.Comment("MaxComponentLength is " + maxComponentLength.ToString());
42            }
43
44            object nullCheckAvailability = m_testContext.DataRow["Availability"];
45            if (nullCheckAvailability.GetType() == typeof(DBNull))
46            {
47                Log.Comment("Availability is NULL");
48            }
49            else
50            {
51                UInt32 availability = (UInt32)nullCheckAvailability;
52                Log.Comment("Availability is " + availability.ToString());
53            }
54        }
55        ...
56        public TestContext TestContext
57        {
58            get { return m_testContext; }
59            set { m_testContext = value; }
60        }
61
62        private TestContext m_testContext;
63    }
64}

例如,在上述测试中,在某些情况下,“Compressed”、“MaximumComponentLength”和“Availability”可以为 null(当查询返回可移动驱动器(如软盘驱动器)时)。 你希望确保测试在此类情况下的行为适当。 为此,以对象的形式获取属性值,并检查其类型是否为“DBNull”。 如果是,则表示返回的属性值为 null。 如果不是,则返回的值不为 null,因此有效 - 因此将其强制转换为适当的类型,并将其用于测试。

本机检索 API 也是如此 - 返回的属性值可以为 NULL。 这意味着,你需要检查 TestData 是否成功检索了该值而不使用验证调用(因为无法检索可能是因为该值为 null)。 例如,你可能具有依赖于 WMI 查询的测试方法:

1        // Test on only local (drive type = 3) or removable (drive type = 2) harddrive
2        BEGIN_TEST_METHOD(LocalOrRemovableHardDriveTest)
3            TEST_METHOD_PROPERTY(L"DataSource", L"WMI:SELECT DeviceId, DriveType, Availability,
                  MaximumComponentLength FROM Win32_LogicalDisk WHERE DriveType=2 OR DriveType=3")
4        END_TEST_METHOD()

可能会将“Availability”和“MaximumComponentLength”返回为 NULL 值。 因此,请编写测试以如下方式考虑这一点:

1     void WmiExample::LocalOrRemovableHardDriveTest()
2     {
3         String deviceId;
4         VERIFY_SUCCEEDED(TestData::TryGetValue(L"DeviceId", deviceId));
5         int driveType;
6         VERIFY_SUCCEEDED(TestData::TryGetValue(L"DriveType", driveType));
7
8         unsigned int maxComponentLength;
9         if (SUCCEEDED(TestData::TryGetValue(L"MaximumComponentLength", maxComponentLength)))
10        {
11            Log::Comment(String().Format(L"MaximumComponentLength: %d", maxComponentLength));
12        }
13
14        unsigned int availability;
15        if (SUCCEEDED(TestData::TryGetValue(L"Availability", availability)))
16        {
17            Log::Comment(String().Format(L"Availability: %d", availability));
18        }
19
20        Log::Comment(L"DeviceId: " + deviceId);
21        Log::Comment(String().Format(L"DriveType: %d", driveType));
22    }