使用 Nunit TestCaseSource 运行 测试设置的正确方法

Correct Way To Run Test Set Up with Nunit TestCaseSource

我正在尝试 运行 在 NUnit 中使用 TestCaseSource 进行多项测试。但是我正在努力让 [SetUp] 在我想要的时候达到 运行。

目前它按我想要的方式工作,但感觉不对 "right"。所以下面是主要的测试用例代码(简化):

public class ImportTestCases
{

    ImportTestCases()
    {
        TestData.RunTestSetup();
    }

    public static IEnumerable TestCases
    {
        get
        {
            //run the funciton under test...
            var results = RunFunctionSubjectToTest(TestData.ImportantVar);

            //get multiple results...
            var allProperties =new TestCaseData(o).Returns(true)
                ExpandNestedProperties(results.AllProperties)
                    .ToList()
                    .ConvertAll(o => new TestCaseData(o).Returns(true));

            return allProperties;
        }
    }


}


[TestFixture]
public class ImportTests
{

    [TestFixtureSetUp]
    public void ImporTestSetup()
    {
        TestData.RunTestSetup();
    }

    [Test, TestCaseSource(typeof(ImportTestCases), nameof(ImportTestCases.TestCases))]
    public bool PropertyTest(UnitTestHelper.PropInfo info)
    {
        return info.DoTheyMatch;
    }

}

这里的麻烦是 [SetUp] 在 ImportTestCases "TestCases" 之前 运行 属性 "get" 是 运行。 "ImportTestCases" 的构造函数也不是 运行。因此,为了确保在引用 ImportVar 之前 "RunTestSetup" 是 运行,我必须执行以下操作:

public static class TestData
{
    private static bool HasSetUpRan = false;
    private static int _importantVar;
    public static int ImportantVar
    {
        get
        {
            if(!HasSetUpRan)
            {
                RunTestSetup();
            }
            return _importantVar;
        }
    }        
    public static void RunTestSetup()
    {
        if (HasSetUpRan)
        {
            return;
        }
        ///do set up
        //e.g. _importantVar = GenerateId();
        //end
        HasSetUpRan= true;
    }

}

如您所见,这可确保 Set up 在返回变量之前具有 运行。遗憾的是,这是迄今为止我设法让它工作的唯一方法。正如我所说,感觉 "wrong" 并且过于复杂。也许我在这里过度使用了测试用例?或者我应该使用某种参数化测试用例(这可能吗?)。

我已尝试简化上面的代码,如果我尝试测试的代码根本没有意义,我深表歉意。

要点在创建 TestCaseSources 之前是否有 运行 的[设置]?

也许一种可能的解决方案是使创建 TestSource 的方法不是静态的,并添加默认构造函数。在构造函数中,您可以执行测试用例所需的所有初始化工作。您仍然可以将 TestFixtureSetUp 用于其他初始化内容。

[TestFixture]
public class ImportTests
{
    public ImportTests()
    {
        //inititalize test case source
    }

    [TestFixtureSetUp]
    public void ImporTestSetup()
    {
        //inititalize rest of test
    }

    public IEnumerable<string> Fields()
    {
        return new[] { "foo", "bar", "foobar" };
    }

    [Test]
    [TestCaseSource("Fields")]
    public void PropertyTest(string info)
    {
       // Assert
    }
}

要点是测试用例将在加载测试时定位。因此具有 [TestFixtureSetUp] 属性的例程将在调用“TestCases” 属性 之后执行。但是,您可以在静态构造函数中执行一些设置例程。但是为了先调用它,你需要把你的测试数据放在同一个 class:

[TestFixture]
public class ImportTests
{
    static ImportTests() 
    {
        //Step 1 
        //run your set-up routine
    }

    //Step 3
    [Test, TestCaseSource(nameof(ImportTests.TestCases))]
    public bool PropertyTest(string s) => string.IsNullOrEmpty(s);

    //Step 2
    public static IEnumerable TestCases => new[] {new TestCaseData("").Returns(true)};
}

您尝试做的基本上忽略了 NUnit 的工作原理。考虑这个序列:

  1. 您 运行 一个 GUI 并加载一个测试程序集。
  2. 为了给 gui 一个测试列表,NUnit 执行你的 TestCaseSource 方法。
  3. 您的测试用例源方法决定了将有多少个测试以及传递给每个测试用例的参数。
  4. 您在办公桌前坐了很长时间,查看所有测试的名称,或者可能在 Twitter 上发帖。假设 20 分钟。
  5. 您 运行 所有测试,导致 SetUp、TearDown 和测试方法本身执行。
  6. 你去吃午饭。
  7. 午餐后,您决定再次 运行 测试。所有相同的 SetUp、TestMethod、TearDown 东西再次执行。只要您没有重新编译和重新加载测试程序集,就不会再次使用测试用例源。

在决定在测试用例源代码中做什么时,您必须注意这个序列,我为了效果而夸大了它。通常,创建长期存在的对象可能不是您想要做的。

更多注意事项:

  • 在NUnit中,测试用例几乎总是参数化的。如果测试没有参数化,那么它不需要源,只是一个简单的测试,可以完成所有自己的初始化。

  • 将相同的方法标记为有人建议的测试和来源是一个非常大的错误 - 病态。我们没有将其视为错误的唯一原因是几乎没有人尝试这样做,因为测试方法和测试用例源方法的目的完全不同。

您最好的选择是使用 return 参数的来源,测试可以使用这些参数来实例化您需要的对象。

将测试方法参数设置为Func<YOURParamater>。 因此,当 Nunit 需要获取测试用例列表时 return Func 不是实参。 在测试 运行 时间调用此 Func 并获取您的参数。

Wrap 只是一个 return lambda 为 Func 的函数。 bcs lambda 不能转换为对象,与 Func.

相反

如果您觉得这个答案不令人满意。请先说明原因再给它打分。

public class TestCaseClass
{
    public static IEnumerable Cases
    {
        get
        {

            yield return new TestCaseData(Wrap(() => 1)).SetArgDisplayNames("1");
            yield return new TestCaseData(Wrap(() => 2)).SetArgDisplayNames("2");
        }
    }
    private static Func<T> Wrap<T>(Func<T> fun)
    {
        return fun;
    }
}


[TestCaseSource(typeof(TestCaseClass), nameof(UserTestCases.Cases))]
public bool Tests(Func<int> getInt)
{
    return getInt() == 1;
}