在调用测试方法之前,我们如何静态初始化测试数据?(How do we statically initialize test data before calling a test method?)

编程入门 行业动态 更新时间:2024-10-22 12:29:50
在调用测试方法之前,我们如何静态初始化测试数据?(How do we statically initialize test data before calling a test method?)

我们有以下测试设置。

Permutations.Tests.fsproj

<ItemGroup> <Compile Include="Permute1Tests.fs" /> <Compile Include="Permute2Tests.fs" /> </ItemGroup>

Permute1Tests.fs

module Permute1Tests open Xunit open Permutations.Permute1 [<Theory>] [<MemberData("permuteTestValues")>] let ``permute`` (x, expected) = let actual = permute x Assert.Equal<List<int>>(expected, actual); let permuteTestValues : obj array seq = seq { yield [| [0;1]; [[0;1]; [1;0]] |] }

Permute2Tests.fs

module Permute2Tests open Xunit open Permutations.Permute2 [<Theory>] [<MemberData("removeFirstTestData")>] let ``removeFirst`` (item, list, expected: List<int>) = let actual = removeFirst list item Assert.Equal<List<int>>(expected, actual) let removeFirstTestData : obj array seq = seq { yield [| 0; [1;2;3;4]; [1;2;3;4] |] }

当我们运行dotnet test ,这是错误:

System.InvalidOperationException:Permute2Tests.removeFirst为测试数据返回null。 在调用此测试方法之前,请确保它已静态初始化。

奇怪的是, Permute1Tests.fs运行没有错误。 它的测试通过。 并且,如果我们使用Permute1Test.fs交换ItemGroup的Permute1Test.fs位置,那么后者现在可以工作而前者有错误。

在调用测试方法之前,我们如何静态初始化测试数据? 似乎ItemGroup命令在我们当前的方法中很重要,这使我们当前的方法失败。

上面代码的完整版本在这里 。

编辑:ILSpy输出

Permute1Tests.fs.cs

// <StartupCode$Permutations-Tests>.$Permute1Tests using <StartupCode$Permutations-Tests>; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; internal static class $Permute1Tests { [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal static readonly IEnumerable<object[]> permuteTestValues@12; [DebuggerBrowsable(DebuggerBrowsableState.Never)] [CompilerGenerated] [DebuggerNonUserCode] internal static int init@; static $Permute1Tests() { IEnumerable<object[]> permuteTestValues = $Permute1Tests.permuteTestValues@12 = (IEnumerable<object[]>)new Permute1Tests.permuteTestValues@14(0, null); } }

Permute2Tests.fs.cs

// <StartupCode$Permutations-Tests>.$Permute2Tests using <StartupCode$Permutations-Tests>; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; internal static class $Permute2Tests { [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal static IEnumerable<object[]> removeFirstTestData@15; [DebuggerBrowsable(DebuggerBrowsableState.Never)] [CompilerGenerated] [DebuggerNonUserCode] internal static int init@; public static void main@() { IEnumerable<object[]> removeFirstTestData = $Permute2Tests.removeFirstTestData@15 = (IEnumerable<object[]>)new Permute2Tests.removeFirstTestData@17(0, null); } }

Permutations.Test.fsproj

<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netcoreapp2.0</TargetFramework> <IsPackable>false</IsPackable> </PropertyGroup> <ItemGroup> <Compile Include="Permute1Tests.fs" /> <Compile Include="Permute2Tests.fs" /> </ItemGroup> <ItemGroup> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" /> <PackageReference Include="xunit" Version="2.3.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" /> <DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\Permutations\Permutations.fsproj" /> </ItemGroup> </Project>

We have the following test set up.

Permutations.Tests.fsproj

<ItemGroup> <Compile Include="Permute1Tests.fs" /> <Compile Include="Permute2Tests.fs" /> </ItemGroup>

Permute1Tests.fs

module Permute1Tests open Xunit open Permutations.Permute1 [<Theory>] [<MemberData("permuteTestValues")>] let ``permute`` (x, expected) = let actual = permute x Assert.Equal<List<int>>(expected, actual); let permuteTestValues : obj array seq = seq { yield [| [0;1]; [[0;1]; [1;0]] |] }

Permute2Tests.fs

module Permute2Tests open Xunit open Permutations.Permute2 [<Theory>] [<MemberData("removeFirstTestData")>] let ``removeFirst`` (item, list, expected: List<int>) = let actual = removeFirst list item Assert.Equal<List<int>>(expected, actual) let removeFirstTestData : obj array seq = seq { yield [| 0; [1;2;3;4]; [1;2;3;4] |] }

When we run dotnet test, this is the error:

System.InvalidOperationException : Test data returned null for Permute2Tests.removeFirst. Make sure it is statically initialized before this test method is called.

Oddly enough, Permute1Tests.fs runs without error. Its test passes. And, if we swap the Permute1Test.fs position in the ItemGroup with the Permute2Test.fs, then the latter now works and the former has the error.

How do we statically initialize test data before calling a test method? It seems that ItemGroup order matters in our current approach, and that makes our current approach fail.

The full version of the above code is here.

Edit: ILSpy Output

Permute1Tests.fs.cs

// <StartupCode$Permutations-Tests>.$Permute1Tests using <StartupCode$Permutations-Tests>; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; internal static class $Permute1Tests { [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal static readonly IEnumerable<object[]> permuteTestValues@12; [DebuggerBrowsable(DebuggerBrowsableState.Never)] [CompilerGenerated] [DebuggerNonUserCode] internal static int init@; static $Permute1Tests() { IEnumerable<object[]> permuteTestValues = $Permute1Tests.permuteTestValues@12 = (IEnumerable<object[]>)new Permute1Tests.permuteTestValues@14(0, null); } }

Permute2Tests.fs.cs

// <StartupCode$Permutations-Tests>.$Permute2Tests using <StartupCode$Permutations-Tests>; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; internal static class $Permute2Tests { [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal static IEnumerable<object[]> removeFirstTestData@15; [DebuggerBrowsable(DebuggerBrowsableState.Never)] [CompilerGenerated] [DebuggerNonUserCode] internal static int init@; public static void main@() { IEnumerable<object[]> removeFirstTestData = $Permute2Tests.removeFirstTestData@15 = (IEnumerable<object[]>)new Permute2Tests.removeFirstTestData@17(0, null); } }

Permutations.Test.fsproj

<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netcoreapp2.0</TargetFramework> <IsPackable>false</IsPackable> </PropertyGroup> <ItemGroup> <Compile Include="Permute1Tests.fs" /> <Compile Include="Permute2Tests.fs" /> </ItemGroup> <ItemGroup> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.5.0" /> <PackageReference Include="xunit" Version="2.3.1" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.3.1" /> <DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\Permutations\Permutations.fsproj" /> </ItemGroup> </Project>

最满意答案

为什么

这与您的程序集是“可执行文件”(即具有入口点的程序)而不是“库”这一事实有关。

F#编译可执行文件与库略有不同:在入口点所在的模块中,所有静态数据在main函数内初始化,然后执行其他所有操作; 在所有其他模块中,静态数据在静态构造函数中初始化。 我不确定这个决定背后的原因是什么,但这就是F#编译器的行为方式。

接下来,F#编译器如何确定哪个模块包含入口点? 非常简单:无论哪个模块是最后一个模块,这都是入口点所在的位置。 想一想,这是唯一明智的选择:因为F#具有编译顺序,所以只有最后一个文件才能访问所有其他文件中的定义; 因此,这就是入口点必须的地方。

因此,在您的示例中,无论哪个模块在列表中的最后一个,最后都有一个main函数,其中定位了静态初始化代码。 由于单元测试运行器在执行测试之前未运行入口点,因此该模块中的静态数据仍未初始化。

解决方案1:添加一个人工模块以包含入口点

正如您已经发现的那样,一种解决方案是添加一个只包含入口点的人工模块。 这样,测试模块将不再是最后一个,不包含入口点,因此它的数据将在静态构造函数中初始化。

人工模块甚至不必具有[<EntryPoint>] main函数,它可能只是这样:

module Dummy let _x = 0 // `do ()` would be even shorter, but that will create a warning

无论如何,编译器将添加一个入口点。

解决方案2:编译为netstandard2.0

如果将目标从netcoreapp2.0切换到netstandard2.0 ,则程序集将被视为“库”而不是“可执行文件”,并且编译器不会添加入口点,也不会在其中放置静态初始化。

Why

This has to do with the fact that your assembly is an "executable" (i.e. a program with an entry point) rather than a "library".

F# compiles executables slightly differently than libraries: in the module where the entry point is located, all static data is initialized inside the main function, before executing everything else; in all other modules, static data is initialized in static constructors. I am not sure what was the reasoning behind this decision, but this is how the F# compiler behaves.

Next, how does the F# compiler determine which module contains the entry point? Very simple: whichever module is the last one, that's where the entry point is. To think of it, this is the only sensible choice: since F# has compilation order, only the very last file can have access to definitions in all other files; therefore, that's where the entry point must be.

So, in your example, whichever module was last in the list, ended up with a main function, in which the static initialization code was located. And since the unit test runner doesn't run the entry point before executing tests, the static data in that module remained uninitialized.

Solution 1: add an artificial module to contain the entry point

As you have already discovered yourself, one solution is to add an artificial module that contains nothing but the entry point. That way, the test module won't be the last one anymore, won't contain the entry point, and therefore its data will be initialized in the static constructor.

The artificial module doesn't even have to have an [<EntryPoint>] main function, it could just be this:

module Dummy let _x = 0 // `do ()` would be even shorter, but that will create a warning

The compiler will add an entry point anyway.

Solution 2: compile to netstandard2.0

If you switch your target from netcoreapp2.0 to netstandard2.0, your assembly will be considered a "library" rather than an "executable", and the compiler won't add an entry point, and won't put static initialization in it.

更多推荐

本文发布于:2023-08-07 16:17:00,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1465082.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:初始化   静态   测试数据   测试   方法

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!