【问题标题】:Running automatic acceptance tests on Azure devops using Specflow使用 Specflow 在 Azure devops 上运行自动验收测试
【发布时间】:2019-06-08 13:21:04
【问题描述】:

我正在尝试设置一个 Azure DevOps 项目,该项目在执行构建时运行一些自动验收测试。

我在本地机器上运行验收测试没有问题,但是当我在 DevOps 平台上执行构建时,测试似乎运行不正确。我的单元测试按预期运行,但验收测试根本没有运行。这是 TestAssemblies 的完整输出日志:https://pastebin.com/ZCx1RdGp

编辑:删除 Runner 和 SpecFlow 包后,我得到以下输出:https://pastebin.com/tTtKEcX5

最能打动我的是下面的几行。似乎它正在尝试运行测试,但它只是不正确:

2019-01-14T12:22:38.9295662Z Profile: Acceptance
2019-01-14T12:22:38.9324097Z starting test run
2019-01-14T12:22:38.9324763Z Discovering target: Default
2019-01-14T12:22:39.0023466Z test run finished
2019-01-14T12:22:39.0025670Z publishing test results
2019-01-14T12:22:39.0026124Z test results published
2019-01-14T12:22:39.0026476Z generating reports
2019-01-14T12:22:39.0026874Z creating 1 report(s)
2019-01-14T12:22:39.0027228Z generate Report ReportTemplate.cshtml
2019-01-14T12:22:40.2872092Z reports generated
2019-01-14T12:22:40.2876384Z Result: tests executed with warnings
2019-01-14T12:22:40.2876686Z   Total: 0
2019-01-14T12:22:40.2876843Z   Succeeded: 0
2019-01-14T12:22:40.2876982Z   Ignored: 0
2019-01-14T12:22:40.2877128Z   Pending: 0
2019-01-14T12:22:40.2877260Z   Skipped: 0
2019-01-14T12:22:40.2877390Z   Failed: 0
2019-01-14T12:22:40.2877504Z

我的项目中包含以下包:

  • Nunit 包
  • SpecFlow 包
  • NUnit3TestAdapter
  • SpecRun.Runner
  • SpecRun.SpecFlow

我的构建管道看起来像这样,我没有对任何任务做任何事情,我猜我缺少测试程序集上的一些设置,但我不确定它可能是什么。据我所知,只要它试图构建的程序集中有正确的包,它就应该能够默认测试它:

我真的希望有人可以在这里为我指明正确的方向,谢谢。

更新基于 Andreas Willich 的回答我做了以下事情

将这些包实施到验收测试项目中

我将测试分为两个任务,一个用于单元测试,另一个用于验收测试。 单元测试在默认的 MSTest.TestAdapter 上运行,并像以前一样成功完成。

我正在尝试让验收测试在 TechTalk.SpecRun.VisualStudio.TestAdapter 上运行

我没有成功完成这项工作。它给了我以下输出:https://pastebin.com/mAR9HK2r

我不确定要在我的测试程序集和我的 csproj 文件中寻找什么,所以我没有设法完成这些步骤。

【问题讨论】:

  • 您安装了 2 个测试运行器。 NUnit 3 和 SpecFlow+Runner。你想用哪一个?
  • 我现在删除了 SpecRun.Runner SpecRun.SpecFlow 包。我更新了问题以反映新的输出日志。好像没有考虑验收测试。

标签: azure-devops specflow


【解决方案1】:

首先,我将使用两个单独的任务进行单元测试和验收测试。这使得可以为它们使用不同的测试适配器(如 NUnit 和 SpecFlow+Runner)。

如果您使用 SpecFlow 进行验收测试,则需要 SpecFlow 包。您只需选择您使用的测试适配器。根据您要使用的测试适配器查看此答案您需要哪个包:https://stackoverflow.com/a/38990326/3155323

另外我会将程序集过滤器限制为您测试程序集。这大大减少了注销,因为测试适配器必须扫描较少的程序集。

当您切换到 NUnit 时,请确保您已在 app.config 中将 NUnit 配置为 unitTestProvider 并重新生成所有代码隐藏文件。

下一步,我将打开测试程序集并查看生成的测试是否可用。如果没有,请查看您的 csproj 文件,是否将代码隐藏文件添加到 Compile-ItemGroup。

很抱歉,这不是“您忘记选中此框”的答案。当测试适配器没有找到任何测试时,有多种可能的小原因。

【讨论】:

  • 非常感谢您的回答。我尽力去做你告诉我的那些事情,但我没有成功。我根据我的尝试更新了我的问题。
  • 在第二个测试任务中,您必须使用 SpecFlow 场景指定您的测试程序集。您现在只在测试适配器本身中寻找测试(它就像 VSTest 的插件)。
【解决方案2】:

我将发布我为使 SpecFlow 测试结果显示在 Azure DevOps Builds->Tests 区域中所做的工作,但请注意,这是一个giant hack。

我相信 SpecFlow/NUnit 测试应该像常规 NUnit 测试一样得到支持,而且我确实认为它与 Andreas Willich 所说的测试适配器配置有关,但是我都无法获得它对我有用,我也找不到任何人通过正常管道配置使其工作的示例。 SpecFlow+ 可能也有工作方式,但我不使用 plus 版本。如果/当我能学会正确的方法来做到这一点时,我将停止使用下面的方法。

  1. 创建一个简单的过程,以 CSV 格式将 SpecFlow 测试名称和测试结果值写入光盘。我这样做是因为我想将功能测试解决方案与这个 hack 发生的任何其他逻辑分开。这个过程应该很简单,就像使用 [AfterScenario] 挂钩步骤从 Scenario.Context 中提取场景标题和结果值 - 或者您可以获取它们的任何地方 - 并将它们以逗号分隔值格式写入文本文件。

  2. 将该 CSV 结果记录过程集成到“SpecFlow”项目中,该项目包含您希望其结果显示在 Azure DevOps 中的测试。

CSV 应该如下所示:

DemoScenario_01 Lorem ipsum dolor sit amet consectetur adipiscing elit, Pass
DemoScenario_02 Sed do eiusmod tempor incididunt ut labore et dolore, Pass
DemoScenario_03 Magna aliqua Ut enim ad minim veniam quis, Pass
DemoScenario_04 Nostrud exercitation ullamco laboris nisi ut aliquip, Pass
DemoScenario_05 Ex ea commodo consequat Duis aute irure dolor in, Pass
DemoScenario_06 Reprehenderit in voluptate velit esse cillum dolore eu, Pass
DemoScenario_07 Fugiat nulla pariatur Excepteur sint occaecat cupidatat, Pass
DemoScenario_08 Non proident sunt in culpa qui officia semper, Pass
DemoScenario_09 Deserunt mollit anim id est laborum arcu semper, Pass
DemoScenario_10 Orci a scelerisque purus semper eget Ornare arcu dui vivamus, Pass
  1. 创建一个单独的简单“虚拟”项目,其中仅包含一个类,该类具有一个工作 NUnit“测试”方法,除了 Assert.Pass() 之外什么都不做。此项目需要安装 NUnit 和 NUnit3TestAdapter NuGet 包。

这个类应该是这样的:

    namespace DemoNunit
    {
        public class Tests
        {
            [Test]
            public static void DemoTest005()
            {
                Assert.Pass();
            }
        }
    }
  1. 为此“虚拟”项目的解决方案创建一个 Azure DevOps Git 代码存储库并将其推送到存储库。

  2. 创建一个为 CI 构建配置的新 Azure 管道,当“虚拟”NUnit 项目的提交被推送到其存储库时,该管道将自动触发。将管道配置为具有一个名为“SpecFlow Tests”的 Visual Studio 测试步骤,该步骤会查找包含该单个 NUnit 测试的 dll。根据您的“SpecFlow”项目或其测试的功能命名此管道,因为此管道实际上最终会在 Builds->Tests 区域中显示这些结果。

  3. 在一个新的单独的“convert”项目中,创建一个进程,该进程将从“SpecFlow”项目中读取简单的 CSV 输出结果文件,并在“ dummy”项目,包含 NUnit 测试。

“虚拟”项目中的单个 NUnit 测试方法现在将替换为多种方法,一种用于记录在 CSV 中的每个结果。这些方法将被命名为 guid 减去任何破折号,并以一个字母作为前缀,以使它们成为有效的 C# 方法名称。目标只是拥有不重复的方法名称。实际的 SpecFlow 测试场景名称将存储在 NUnit TestCase TestName 属性中。 Assert.Pass() 或 Assert.Fail() 将根据从 CSV 文件中读取的相关结果值使用。将此“转换”项目编译为 exe。

我省略了读取 CSV 结果的代码...

    namespace CreateCsFile
    {
        public static class CsFile
        {
            public static string OpenClass = 
                "using NUnit.Framework;" +
                "namespace DemoNunit" +
                "{" +
                "    public class Tests" +
                "    {";
            public static string CloseClass =
                "    }" +
                "}";
            public static string TestMethod =
                "        [Test, TestCase(TestName = \"UniqueNameAttribute\")]" +
                "        public static void MethodName()" +
                "        {" +
                "            Assert.Result();" +
                "        }";
            public static void LogListOfResults(
                List<Test> resultsList)
            {
                Log.CsharpFile(OpenClass);
                foreach (var result in resultsList)
                {
                    Outcome.IsValid(result.Result);
                    var testMethod =
                        TestMethod.Replace(
                                "UniqueNameAttribute",
                                result.Name).
                            Replace(
                                "Result",
                                result.Result).
                            Replace("MethodName",
                                "a" + Guid.NewGuid().
                                    ToString().
                                    Replace("-",""));
                    Log.CsharpFile(testMethod);
                }
                Log.CsharpFile(CloseClass);
            }

            public static void ConvertCsvResultsToNunitResults()
            {
                LogListOfResults(
                    ParseCsv.ResultsSheet());
            }
        }
    }

日志类...

namespace CreateCsFile
{
    public class Log
    {
        public static string WasFileRemoved
            = "";
        public static string CsFileToggle
            = "True";
        public static string CsFile
            = "C:\\Projects\\DemoNunit\\Tests.cs";
        public static void CsharpFile(
            string nunitData)
        {
            if (CsFileToggle.ToUpper()
                == "TRUE")
            {
                if (string.IsNullOrEmpty(
                    WasFileRemoved))
                {
                    RemoveExistingCsFile();
                }
                var log = !File.Exists(CsFile) ?
                    new StreamWriter(CsFile) :
                    File.AppendText(CsFile);
                log.WriteLine(nunitData);
                log.Close();
            }
        }

        public static void RemoveExistingCsFile()
        {
            if (CsFileToggle.ToUpper()
                == "TRUE")
            {
                WasFileRemoved = "True";
                try
                {
                    var fileInfo = 
                        new FileInfo(CsFile);
                    fileInfo.Attributes =
                        FileAttributes.Normal;
                    File.Delete(fileInfo.FullName);
                }
                catch
                {
                    throw new Exception(
                        @"Unable to delete existing csharp file...");
                }
            }
        }
    }
}

丑陋的输出:

namespace DemoNunit{    public class Tests    {
        [Test, TestCase(TestName = "DemoScenario_01 Lorem ipsum dolor sit amet consectetur adipiscing elit")]        public static void aa5f7fd239d6a40878780bc6c81f3a18b()        {            Assert. Pass();        }
        [Test, TestCase(TestName = "DemoScenario_02 Sed do eiusmod tempor incididunt ut labore et dolore")]        public static void aa9882fa95b17499eb9386b20a7ff303d()        {            Assert. Pass();        }
        [Test, TestCase(TestName = "DemoScenario_03 Magna aliqua Ut enim ad minim veniam quis")]        public static void a440c25f8c3c24e92ad90224da56bafda()        {            Assert. Pass();        }
        [Test, TestCase(TestName = "DemoScenario_04 Nostrud exercitation ullamco laboris nisi ut aliquip")]        public static void ab2c3cc6997df4a42b0992128f63358f7()        {            Assert. Pass();        }
        [Test, TestCase(TestName = "DemoScenario_05 Ex ea commodo consequat Duis aute irure dolor in")]        public static void a2c9f744dcd2c42c99cb6e288cf09fc78()        {            Assert. Pass();        }
        [Test, TestCase(TestName = "DemoScenario_06 Reprehenderit in voluptate velit esse cillum dolore eu")]        public static void a294422ae029049f9ac4be6f9bb4529cc()        {            Assert. Pass();        }
        [Test, TestCase(TestName = "DemoScenario_07 Fugiat nulla pariatur Excepteur sint occaecat cupidatat")]        public static void aa2dcdf889ffe4e46b57a73882d6f1a68()        {            Assert. Pass();        }
        [Test, TestCase(TestName = "DemoScenario_08 Non proident sunt in culpa qui officia semper")]        public static void a891c43376a5049f89ad75b70fa0a543f()        {            Assert. Pass();        }
        [Test, TestCase(TestName = "DemoScenario_09 Deserunt mollit anim id est laborum arcu semper")]        public static void aa8da317895214abc966c229c832c162f()        {            Assert. Pass();        }
        [Test, TestCase(TestName = "DemoScenario_10 Orci a scelerisque purus semper eget Ornare arcu dui vivamus")]        public static void aa9accb0c9c1b4918b76bc75ff2f2e835()        {            Assert. Pass();        }
    }}

现在执行以下步骤:

  • 使用命令行通过命令行执行“SpecFlow”项目的测试 NUnit 控制台 TestRunner(它应该被配置为删除 CSV 输出到某个地方,如 c:\Temp)
  • 执行您的“转换”项目,该项目应配置为读取 c:\Temp 中的 CSV 并更新“虚拟”项目中的 .cs 文件 其克隆的 repo 存在于磁盘上的任何位置
  • 通过命令行参数,有 git commit 并推送到 repo 将“转换”exe 更改为“虚拟”项目的 .cs 文件

Azure 管道现在将使用它存储在 CSV 中的名称和结果构建“SpecFlow”项目的测试执行结果并将其显示为“SpecFlow 测试”(我的也有一些演示 ID)

可以通过不同的方式进行设置;一种方法是:

  • 将您的“SpecFlow”项目放入 git 存储库并创建 CI 管道 为它
  • 添加构建步骤集以通过命令行执行测试
  • 添加一个步骤来调用已放入 修复构建框上的位置并更新“虚拟”项目的文件,该文件位于共享位置的单独 VM 中
  • 添加一个步骤,将“虚拟”项目的最新更新推送到 git 存储库

此管道现在将在提交推送到其存储库时自动触发,并且该过程将自动将 SpecFlow 结果转换为 NUnit 结果,触发辅助管道,并在该秒的 Builds->Tests 区域中显示 SpecFlow 结果管道。第一个管道不会显示任何结果,您总是会查看第二个管道来查看它们。

以这种方式支持更多/其他“SpecFlow”项目......他们可能都共享“转换”部分,但需要改进以能够将 . cs 文件正在更新。然后,您可以为您希望以这种方式查看结果的每个“SpecFlow”项目创建单独的“虚拟”项目(当然还有相关的存储库/管道)。我还没有走到这一步,因为我目前只为一个项目做这件事。

【讨论】:

    猜你喜欢
    • 2020-12-24
    • 1970-01-01
    • 1970-01-01
    • 2022-08-16
    • 2012-10-12
    • 2021-06-16
    • 2021-03-08
    • 2021-12-09
    • 2019-03-15
    相关资源
    最近更新 更多