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

10

我的一些单元测试中有一个在循环中定义的“Sleep”。我希望不仅能够对每个测试迭代进行分析,还能对所有迭代的总时间进行分析,以显示任何非线性缩放。例如,如果我对“Overall”进行分析,则包括了等待时间。我可以使用 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
我在示例代码中设置了一个固定的睡眠时间,但实际上它是一个从1秒到24秒的随机睡眠时间,这相当长 :( - esac
睡眠时间必须是随机的。固定的睡眠时间会导致在多个用户测试时出现活动/不活动的波动,这并不准确。 - esac
我不需要这样做。在循环之前,我可以使用Stopwatch.StartNew();在doAction()之前,我只需调用Start(),在sleep调用之前调用Stop()。除非我进行重置,否则它将持续增加Stopwatch的时间,而不包括Sleep()。这就是我想记录的时间。 - esac
3个回答

9

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

首先,你正在尝试的方式是行不通的。负载测试和单元测试之间没有直接链接,而TestContext存在于单元测试中。

其次,你需要了解Visual Studio如何创建报告。它会收集来自操作系统的性能计数器数据。你可以编辑这些计数器,删除你不想要的,添加你想要的。

如何编辑计数器

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

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

  • 计数器集映射。在此处,您将计数器集与您的计算机关联起来。默认情况下,[控制器计算机][代理计算机]添加了一些默认的计数器集。这意味着将从您的控制器计算机中收集映射到[控制器计算机]的计数器集中包含的所有计数器。对于所有代理也适用同样的规则。

enter image description here

你可以添加更多的计数器集和更多的机器。通过右键单击 计数器集映射 --> 管理计数器集... 打开一个新窗口,如下所示:

enter image description here

如您所见,我添加了一个名为db_1的额外机器。这是该计算机的计算机名称,必须与控制器处于同一域中,以便访问它并收集计数器。我还将其标记为数据库服务器并选择了sql计数器集(适用于sql计数器的默认值,但您可以编辑它并添加任何计数器)。现在每次执行此负载测试时,控制器都会转到具有计算机名称db_1的计算机,并收集数据,这些数据将在最终测试结果中报告。

现在是编程部分

好的,在这个(庞大的)介绍之后,现在是时候看看如何将您的数据添加到最终的测试结果中了。为了做到这一点,您必须创建自己的自定义性能计数器。这意味着必须在需要收集这些数据的机器上创建一个新的性能计数器类别。在您的情况下,这是在所有代理商中,因为这是执行UnitTests的地方。

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

以下是如何执行此操作的示例代码。

首先,为所有代理商创建性能计数器。仅在每个代理机器上运行此代码一次(或者您可以将其添加到负载测试插件中):

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);
     }
}

现在,当您的测试被执行时,它们将向计数器报告其数据。在负载测试结束时,您将能够在每个代理机器中查看计数器并将其添加到图表中。它将报告最小值、最大值和平均值。

结论

  1. 经过数月的研究,我认为这是将测试自定义数据添加到最终负载测试报告中的唯一方法。
  2. 这可能看起来很难做到。好吧,如果您理解了要点,优化它并不难。我将此功能封装成一个类,以便更容易地初始化、更新和管理计数器。
  3. 它非常实用。现在我可以看到从默认计数器无法看到的测试统计信息,例如,当 Web 服务的 Web 请求失败时,我可以捕获错误并更新适当的计数器(例如超时、服务不可用、请求被拒绝...)。

希望我能帮到您。:)


我喜欢这个解决方案。我已经为我的SUT实现了一些客户性能计数器类别/计数器。没有想到要扩展它到这个领域。我希望能在TestContext计时器的同一区域看到它,但我不会挑剔。有什么想法可以将计数器添加到摘要报告中吗?这是我能想到的唯一缺点。 - esac
总结报告是一个固定的报告,我认为不可能进行自定义。然而,我并不经常使用它。Visual Studio负载测试的真正有趣的功能是Excel报告。这是您真正需要计数器的地方。您将能够比较不同版本的SUT以查看改进,了解您的SUT在不同负载下的反应等等。试试吧,您会发现它有多么强大。 - chaliasos
另外,我还有一个问题要问你。我不明白为什么你要实现这个循环。我认为你想实现这样一种情况,即用户使用延迟执行“doAction”5次。但这已经实现了。在负载测试中使用“思考时间”功能,这样每个虚拟用户在开始另一个测试之前都会等待几秒钟。 - chaliasos
模拟业务流程。用户将始终创建订单并向订单添加行。行数将会变化,每行之间的“思考时间”也会不同。想象一下接听电话订单,对方告诉你一个项目,你输入它,然后他们结巴地说“嗯,好吧,我想我会有”,于是第二个项目就加入了。因此,这些行是在循环中完成的,并且由于它们是变量,所以不能分割。 - esac
好的,你比我更清楚你想要测试什么以及如何实施。只是提醒一下,你可以使用“测试混合模型=基于顺序”并添加五次像“TestDoAction”这样的单元测试来获得相同的结果。那样会更清楚你正在测试什么。因为如果你必须在循环的第二次运行后添加一个“DoActionB”,那么你必须编辑你的单元测试。将每个操作放在单独的单元测试中,就可以让你在负载测试中任意组合而无需编辑或添加新内容。 :) - chaliasos
顺便说一下,@Schaliasos写得真好。如果我不想要一个计数器,而只是想要一个场景属性怎么办?例如:当我的TestMethod在负载场景中执行时,我想记录它运行的环境类型:如QA/PreProd/Prod。我会在ClassInitialization或TestMethod本身中填写该属性。我希望之后能够在Excel中进行报告和过滤。 - eetawil

0

我不知道如何将值添加到TestContext中,因此通过该机制保存它。另一种选择可能是将计时结果作为文本写入跟踪、调试或控制台输出流,以便保存在测试运行的日志中。要查看这些输出,需要考虑活动Run Settings的三个Logging属性。它们的默认设置仅保存前200个失败的测试的日志。将Save log frequency for completed tests设置为1应该保存所有测试的日志,直到达到Maximum Test Logs。详细步骤请参见: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://stackoverflow.com/questions/16914487/how-do-i-extract-test-logs-from-visual-studios-load-test-results中描述了这个问题以及我迄今为止所做的工作。

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


这不会报告最小/最大/平均值 - 只会报告每个测试的时间。我必须将其以聚合形式呈现,这将使它成为一个非常手动的过程。 - esac
请评论一下你的更新 - 如果不可能,那么 LoadTest 是如何做到的呢?是否可以模拟 Visual Studio 使用的相同 SPROC/SQL 语句?我想我并不在意是否有“官方”API,只要有一种可行的方法就行。 - esac
在我看来,负载测试管理是因为它是程序内置的基本功能,而不是通过公共API实现的。在Visual Studio中有许多增强功能会很好。您可以尝试在http://visualstudio.uservoice.com/forums/121579-visual-studio上提出您的想法。 - AdrianHHH
TestContext可以从中派生 - 我不知道的是将其编写到数据库中的SQL。 因此,从技术上讲,我可以通过使用公共/私有API的混合来实现,这一点我并不在意。 http://karlz.net/blog/index.php/2012/11/17/where-is-testcontext-begintimer/ - esac

0
最简单的方法是 OP 的原始方法,但我遇到了一些问题,其他人似乎也遇到了同样的问题。其中一个问题是由于某种原因 TestContext.BeginTimer(string) 并不总是存在,可以参考 this 以获取证据,但似乎没有解决方案。但还有另一个问题是错误地创建和使用属性。
  1. 如果您没有一个属性来存储TestContext并尝试使用TestContext.BeginTimer();,您将收到一条消息"Cannot Access Non-Static Method 'BeginTimer' in a static context"。有些人这样做的原因是因为大多数示例将TestContext属性设置为`TestContext TestContext;'。请参见第3点,了解示例使用此属性的原因。
  2. 如果您在ClassInitializeAssemblyInitialize中分配TestContext属性,则似乎会得到一些不太正确的东西,您将获得一个测试上下文的实例,在过去,我对单元测试和编码UI测试没有问题,但负载测试无法处理这个问题。如果您这样做,您将看到一个错误"There is already an active timer with the name 'TimerName' passed to BeginTimer"

  3. 因此,最终的解决方案是确保将TestContext设置为完整属性,如果这样做,测试执行引擎将独立地为每个负载测试运行设置该属性。这意味着您不必自己设置值。

因此,您需要像以下内容一样:

 private TestContext m_testContext;

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

如果您在setter上设置断点,您会发现在Class Initialize之后但在TestInitialize之前,'TestContext setter'被调用并从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");
}

现在当您查看负载测试结果时,您将在“场景”>“测试用例名称”>“事务”>“计时器名称”下看到计时器输出。

这是我的计时器缓存、创建和登录的输出样式。

enter image description here

其中包括:

  • 平均响应时间
  • 平均事务处理时间
  • 总事务数
  • 每秒事务数

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

在 OP 的示例中,如果您使用 10 个用户运行测试,每个用户运行测试 1 次,并且 DoWork 不需要花费任何时间,则会看到:

  • 共进行了 10 次测试
  • "Overall" 的 10 个值,每个值为 5 秒
  • "Per" 的 50 个值,每个值为 0 秒

我认为这是预期的结果。

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

顺便说一句,这是正确实现 TestContext 的方法,以使数据驱动测试正常工作,因为每个测试都可以从上下文中获取其正确的数据。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接