F# 在调用测试方法之前,我们如何静态初始化测试数据?

F# 在调用测试方法之前,我们如何静态初始化测试数据?,f#,.net-core,xunit,F#,.net Core,Xunit,我们设置了以下测试 置换.测试.fsproj <ItemGroup> <Compile Include="Permute1Tests.fs" /> <Compile Include="Permute2Tests.fs" /> </ItemGroup> <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net

我们设置了以下测试

置换.测试.fsproj

<ItemGroup>
  <Compile Include="Permute1Tests.fs" />
  <Compile Include="Permute2Tests.fs" />
</ItemGroup>
<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>
Permute2Tests.fs.cs

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]] |]
    }
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] |]
    }
// <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);
    }
}
// <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);
    }
}
/.$Permute2Tests
使用;
使用System.Collections.Generic;
使用系统诊断;
使用System.Runtime.CompilerServices;
内部静态类$Permute2Tests
{
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
内部静态可数removeFirstTestData@15;
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
[编译生成]
[调试器非用户代码]
内部静态int init@;
公共静态void main@()
{
IEnumerable removeFirstTestData=
$Permute2Tests。removeFirstTestData@15 = 
(IEnumerable)新的Permute2测试。removeFirstTestData@17(0,空);
}
}
置换.Test.fsproj

<ItemGroup>
  <Compile Include="Permute1Tests.fs" />
  <Compile Include="Permute2Tests.fs" />
</ItemGroup>
<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>

netcoreapp2.0
假的
为什么 这与您的程序集是“可执行文件”(即具有入口点的程序)而不是“库”这一事实有关

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

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

因此,在您的示例中,列表中最后一个模块以一个
main
函数结束,静态初始化代码位于该函数中。由于单元测试运行程序在执行测试之前不运行入口点,因此该模块中的静态数据保持未初始化状态

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

人工模块甚至不必有
[]主功能,它可以是这样的:

module Dummy
let _x = 0  // `do ()` would be even shorter, but that will create a warning
无论如何,编译器都会添加一个入口点

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

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

您可以使用ILSpy查看编译后的代码,看看这两个模块是否在某些方面有所不同吗?如果这并没有导致您发现问题,请将ILSpy反编译的结果发布到C#。我可以确认这两个模块没有区别。最不寻常的是,如果我们交换*.fsproj ItemGroup列表中两个文件的顺序,则错误会发生在相反的文件中。这证明了模块之间的唯一区别是它们在ItemGroup列表中出现的顺序@FyodorSoikin你能发布ILSpy反编译的结果吗?@FyodorSoikin我已经添加了ILSpy的输出,它确实提供了一些见解。几乎得到了!您还可以发布您的
fsproj
文件的全部内容吗?