我可以在 Visual Studio 中为 UnitTest/LoadTest 创建自定义 TestContext 计时器吗?

Posted

技术标签:

【中文标题】我可以在 Visual Studio 中为 UnitTest/LoadTest 创建自定义 TestContext 计时器吗?【英文标题】:Can I create a custom TestContext timer for UnitTest/LoadTest in Visual Studio? 【发布时间】:2013-05-29 21:10:18 【问题描述】:

我的一些单元测试有一个在循环中定义的睡眠。我不仅要分析测试的每次迭代,还要分析所有迭代的总时间,以显示任何非线性缩放。例如,如果我配置“整体”,它包括睡眠时间。我可以使用Stopwatch Start/Stop,这样它就只包含 doAction()。但是,我无法将 Stopwatch 结果写入 TestContext 结果。

[TestMethod]
    public void TestMethod1()
    
        TestContext.BeginTimer("Overall");
        for (int i = 0; i < 5; i++)
        
            TestContext.BeginTimer("Per");
            doAction();
            TestContext.EndTimer("Per");
            Sleep(1000);
        
        TestContext.EndTimer("Overall");
    

似乎TestContext可以继承和重新定义。但是,我没有看到任何关于如何将其写回事务存储的示例。

有没有我可以参考的实现,或者其他想法。我希望在 Visual Studio 为 LoadTest 呈现的同一份报告中看到它。否则我必须编写自己的报告。

另外,我尝试嗅探将这些写入 LoadTest 数据库的 SQL,但未能成功找出方法。应该有一个 SPROC 可以调用,但我认为这是测试结束时的所有数据。

【问题讨论】:

我在示例代码中设置了一个固定的睡眠,但它实际上是从 1 秒到 24 秒的随机睡眠,这是相当多的:( 睡眠必须是随机的。固定睡眠意味着您在与多个用户进行测试时会有一波活动/不活动,这是不准确的。 我不需要。在循环之前,我可以做 Stopwatch.StartNew();在 doAction() 之前,我只调用 Start(),在 sleep 调用 Stop() 之前。除非我进行重置,否则它将不断增加秒表的时间,不包括 Sleep()。那是我想记录的时间。 【参考方案1】:

好吧,我遇到了类似的问题。我想像 Visual Studio 一样在最终测试结果中报告我的测试中的一些额外数据/报告/计数器,我找到了解决方案。

首先,您尝试的方式无法做到这一点。负载测试和存在 TestContext 的单元测试之间没有直接联系。

其次,您必须了解 Visual Studio 如何创建报表。它从操作系统的performance counters 收集数据。您可以编辑这些计数器,删除不需要的计数器并添加您想要的其他计数器。

如何编辑计数器

负载测试配置有两个关于计数器的基本部分。它们是:

Counter Sets。这些是一组计数器,例如默认添加的agent。如果您打开此计数器集,您将看到它收集了诸如内存、处理器、物理磁盘等计数器。因此,在测试结束时,您可以看到所有代理的所有这些数据。如果您想向该计数器集添加更多计数器,您可以双击它(从负载测试编辑器,见下图)并选择Add Counters。这将打开一个包含系统所有计数器的窗口并选择您想要的。

Counter Set Mappings。在这里,您将计数器组与您的机器相关联。默认情况下,[CONTROLLER MACHINE][AGENT MACHINES] 添加了一些默认计数器集。这意味着映射到[CONTROLLER MACHINE] 的计数器集中包含的所有计数器都将从您的控制器计算机中收集。这同样适用于您的所有代理。

您可以添加更多计数器组和更多机器。通过右键单击Counter Set Mappings --> Manage Counter Sets... 会打开一个新窗口,如下所示:

如您所见,我添加了一台名为db_1 的额外机器。这是机器的计算机名称,它必须与控制器在同一个域中才能访问它并收集计数器。我还将它标记为database server 并选择了sql 计数器集(sql 计数器的默认设置,但您可以对其进行编辑并添加您想要的任何计数器)。现在每次执行这个负载测试时,控制器都会去一台计算机名为 db_1 的机器上收集数据,这些数据将在最终的测试结果中报告。


现在是编码部分

好的,在这个(大)介绍之后,是时候看看如何将您的数据添加到最终测试结果中了。为此,您必须创建自己的custom performance counters。这意味着必须在您需要收集这些数据的机器中创建一个新的Performance Counter Category。在您的情况下,在您的所有代理中,因为这是执行单元测试的地方。

在代理中创建计数器后,您可以编辑Agents 计数器集,如上所示并选择您额外的自定义计数器。

这是一个关于如何做到这一点的示例代码。

首先为所有代理创建性能计数器。在每台代理机器上只运行一次此代码(或者您可以将其添加到load test plugin):

void CreateCounter() 

    if (PerformanceCounterCategory.Exists("MyCounters"))
    
        PerformanceCounterCategory.Delete("MyCounters");
    

    //Create the Counters collection and add your custom counters 
    CounterCreationDataCollection counters = new CounterCreationDataCollection();
    // The name of the counter is Delay
    counters.Add(new CounterCreationData("Delay", "Keeps the actual delay", PerformanceCounterType.AverageCount64));
    // .... Add the rest counters

    // Create the custom counter category
    PerformanceCounterCategory.Create("MyCounters", "Custom Performance Counters", PerformanceCounterCategoryType.MultiInstance, counters);

这里是你的测试代码:

[TestClass]
public class UnitTest1

    PerformanceCounter OverallDelay;
    PerformanceCounter PerDelay;

    [ClassInitialize]
    public static void ClassInitialize(TestContext TestContext)
    
        // Create the instances of the counters for the current test
        // Initialize it here so it will created only once for this test class
        OverallDelay= new PerformanceCounter("MyCounters", "Delay", "Overall", false));
        PerDelay= new PerformanceCounter("MyCounters", "Delay", "Per", false));
        // .... Add the rest counters instances
    

    [ClassCleanup]
    public void CleanUp()
    
        // Reset the counters and remove the counter instances
        OverallDelay.RawValue = 0;
        OverallDelay.EndInit();
        OverallDelay.RemoveInstance();
        OverallDelay.Dispose();
        PerDelay.RawValue = 0;
        PerDelay.EndInit();
        PerDelay.RemoveInstance();
        PerDelay.Dispose();
    

    [TestMethod]
    public void TestMethod1()
    
         // Use stopwatch to keep track of the the delay
         Stopwatch overall = new Stopwatch();
         Stopwatch per = new Stopwatch();

         overall.Start();

         for (int i = 0; i < 5; i++)
         
             per.Start();
             doAction();
             per.Stop();

             // Update the "Per" instance of the "Delay" counter for each doAction on every test
             PerDelay.Incerement(per.ElapsedMilliseconds);
             Sleep(1000);

             per.Reset();
         

         overall.Stop();

         // Update the "Overall" instance of the "Delay" counter on every test
         OverallDelay.Incerement(overall.ElapsedMilliseconds);
     

现在,当您的测试被执行时,他们将向计数器报告他们的数据。在负载测试结束时,您将能够看到每台代理机器中的计数器并将其添加到图表中。它将以 MIN、MAX 和 AVG 值报告。

结论

    我认为(经过数月的研究)这是将测试中的自定义数据添加到最终负载测试报告中的唯一方法。 这似乎太难了。好吧,如果您理解这一点,那么优化它并不难。我将此功能封装在一个类中,以便于初始化、更新和管理计数器。 它非常非常有用。我现在可以从我的测试中看到使用默认计数器无法实现的统计数据。像我们这样,当对 Web 服务的 Web 请求失败时,我可以捕获错误并更新相应的计数器(例如 Timeout、ServiceUnavailable、RequestRejected...)。

我希望我有所帮助。 :)

【讨论】:

我喜欢这个解决方案。我已经为我的 SUT 实现了一些客户性能计数器类别/计数器。没想到延伸到这个。我想在与 TestContext 计时器相同的区域看到它,但我不会挑剔。知道是否可以将计数器添加到摘要报告中 - 这是我能想到的这种方法的唯一缺点。 摘要报告是固定报告,我认为无法自定义。但是,我不经常使用它。使用 Visual Studio 进行负载测试的真正有趣的功能是 Excel 报告。这是你真的需要计数器。您将能够比较不同版本的 SUT 以了解改进情况、您的 SUT 在不同负载下的反应等。试一试,你会发现它的强大之处。 另外,我还有一个问题要问你。我不明白你为什么要实现循环。我想你想实现一个用户延迟执行doAction 5 次的场景。但这已经实施。在负载测试中使用Think Times 功能,以便每个虚拟用户在开始另一个测试之前等待几秒钟。 模拟业务流程。用户将始终创建订单并向订单添加行。行数会有所不同,并且每行之间的“思考时间”将是可变的。想象一下电话订单,另一端的人给了你一个项目,你输入它,然后他们结结巴巴地说“嗯嗯嗯嗯,我想我会拥有”,所以第 2 项进入了。所以行是在一个循环中完成的,并且由于它们不同,它们不能被拆分。 好吧,你比我更清楚你想测试什么来实现它。只是一个“fyi”,您可以使用 Test Mix Model = Based on sequential order 获得相同的结果,并添加五次 UnitTest,如 TestDoAction。这样,您将更清楚您正在测试什么。因为如果必须在第二次循环运行后添加DoActionB,则必须编辑单元测试。对单独的 UnitTest 进行每个操作将使您可以在负载测试中以任何方式组合,而无需编辑或添加新的。 :)【参考方案2】:

我不知道如何将值添加到 TestContext 并因此通过该机制保存它。另一种方法可能是将计时结果以文本形式简单地写入跟踪、调试或控制台输出流,以便将其保存在测试运行的日志中。要查看这些输出,需​​要考虑活动 Run Settings 的三个 Logging 属性。他们的默认设置只保存前 200 次失败测试的日志。将保存已完成测试的日志频率设置为1应该会保存所有测试的日志,直到达到最大测试日志。步骤更详细地显示在:http://blogs.msdn.com/b/billbar/archive/2009/06/09/vsts-2010-load-test-feature-saving-test-logs.aspx

这种方法的一个缺点是在 Visual Studio 中一次只能看到一个日志文件,方法是单击其中一个结果窗口中的测试日志链接。我一直在尝试找到一种从测试结果的 SQL 数据库中提取 Web 测试日志的方法,而不必在 Visual Studio 中单击每个日志的链接。我相信单元测试日志以相同的方式保存。我已经在https://***.com/questions/16914487/how-do-i-extract-test-logs-from-visual-studios-load-test-results中描述了这个问题以及我迄今为止所管理的内容@

更新。我相信在 Visual Studio 的负载测试环境中直接可用的 API 无法提供问题中所问的内容。可以为 Web 性能测试和单元测试编写数据和诊断适配器。通过使用这样的适配器代码可以记录来自应用程序或测试套件的数据,并将其记录在测试结果中。有几个关于编写数据和诊断适配器的 Microsoft 博客和 MSDN 页面。

【讨论】:

这不会将其报告为 Min/Max/Avg - 仅作为每次测试的时间。我必须以汇总的形式拥有它,这将使它成为一个非常手动的过程。 评论您的更新 - 如果不可能,那么 LoadTest 是如何做到的?不能模拟 Visual Studio 使用的相同 SPROC/SQL 语句吗?我想我不在乎是否有“官方”API,只是一种完成它的方式。 在我看来,负载测试之所以能够管理,是因为它是内置于程序中的基本工具,而不是通过公共 API。在 Visual Studio 中有许多增强功能会很不错。您可以尝试在visualstudio.uservoice.com/forums/121579-visual-studio 上征求您的意见 TestContext 可以派生自——我不知道是将其写入数据库的 SQL。所以从技术上讲,我可以通过混合使用公共/私有 api 来完成,我不介意 karlz.net/blog/index.php/2012/11/17/…【参考方案3】:

最简单的方法是 OP 的原始方法,我似乎遇到了一些陷阱,其他的似乎也是如此。一个是由于某种原因 TestContext.BeginTimer(string);并不总是存在,请参阅this 以获取证据,但似乎没有解决方案。但是还有另一个错误创建和使用属性的问题。

    如果您没有存储TestContext 的属性并尝试使用TestContext.BeginTimer();,您将收到一条消息"Cannot Access Non-Static Method 'BeginTimer' in a static context"。有些人这样做的原因是因为大多数示例的 TestContext 属性为 `TestContext TestContext;'有关示例使用它的原因,请参见 3。

    如果您将TestContext 属性分配给ClassInitializeAssemblyInitialize,您似乎得到了一些不太正确的东西,您会得到一个测试上下文的实例,在过去我有单元测试和编码的 UI 测试没有问题,但负载测试不能处理这个问题。如果你这样做,你会看到一个错误"There is already an active timer with the name 'TimerName' passed to BeginTimer"

    1234563这意味着您不必自己设置值。

所以你需要类似下面的东西:

 private TestContext m_testContext;

    public TestContext TestContext
    
         get  return m_testContext; 
         set  m_testContext = value; 
    

如果您在设置器上设置断点,您将看到在类初始化之后但在TestInitialize 之前,“TestContext 设置器”被调用并从UnitTestExecuter.SetTestContext() 分配了一个值。 现在测试完全按照您尝试的方式进行

public void TestMethod1()

    TestContext.BeginTimer("Overall");
    for (int i = 0; i < 5; i++)
    
        TestContext.BeginTimer("Per");
        doAction();
        TestContext.EndTimer("Per");
        Sleep(1000);
    
    TestContext.EndTimer("Overall");

现在,当您查看负载测试结果时,您将在 Scenario > TestCaseName > Transactions > TimerName 下看到计时器输出

这是我的输出与我的计时器缓存、创建、登录的样子

其中包含

平均响应时间 平均交易时间 总交易次数 事务/秒

所有这些都可以在图表上查看。

在 OP 的示例中,如果您使用 10 个用户运行负载测试,每个用户运行测试 1 次并且 DoWork 花费了 0 秒,您会看到:

总共 10 次测试 “总体”的 10 个值,每个 5 秒, “Per”的 50 个值,每个值为 0 秒。

我认为这是预期的结果。

这些问题我花了几个小时才弄清楚,并经过一些测试来准确定位和验证,但最终这似乎是最简单和最好的解决方案。

附带说明,这是 TestContext 的正确实现,以使数据驱动测试正常工作,因为每个测试都可以从上下文中获取正确的数据。

【讨论】:

以上是关于我可以在 Visual Studio 中为 UnitTest/LoadTest 创建自定义 TestContext 计时器吗?的主要内容,如果未能解决你的问题,请参考以下文章

我可以在 Visual Studio 中为 UnitTest/LoadTest 创建自定义 TestContext 计时器吗?

在 makefile 中为 Visual Studio C++ 构建定义宏

在 html 中为 Visual Studio 中的 Angular 静态键入

在 Visual Studio 2010 C++ 中为 /// 生成 XML 文档注释

在 Visual Studio 2010 中为新 QOBJECT 文件生成 MOC

在 Visual Studio 2015 中为自定义代码分析器抑制警告