F # XUnit тестовые блокировки, когда инициализатор имеет зависимость - PullRequest
2 голосов
/ 29 марта 2019

У меня проблемы с тестом в тестовом проекте ядра netcoreapp2.2 .net.

Перед началом испытаний мне нужно получить некоторые данные, которые будут распределены между тестами.

Однако при запуске следующего теста из командной строки он будет зависать. Выполнение теста следующим образом:

dotnet test --filter "Test async initialization"

Неисправный код выглядит так:

let c = new HttpClient (BaseAddress = (Uri "https://swapi.co/api/people/1/"))    

let luke = 
    async {                
        return! c.GetStringAsync "" |> Async.AwaitTask        
    } |> Async.RunSynchronously

[<Fact>]
let ``Test async initialization`` () =  
    Assert.NotNull(luke)

Хотя, если я поместил создание HttpClient в сборщик luke следующим образом:

let luke = 
    let c = new HttpClient (BaseAddress = (Uri "https://swapi.co/api/people/1/"))
    async {                
        return! c.GetStringAsync "" |> Async.AwaitTask        
    } |> Async.RunSynchronously

[<Fact>]
let ``Test async initialization`` () =  
    Assert.NotNull(luke)

Это означает, что я не могу использовать один и тот же HttpClient для разных сборщиков.

Кто-нибудь знает, что происходит и как разделить одного и того же клиента между несколькими функциями?

1 Ответ

2 голосов
/ 29 марта 2019

Проблема вызвана тем, что код «инициализации» на самом деле не является кодом инициализации.Это всего лишь два статических поля, которые будут оцениваться только по запросу.Если вы отладите модульный тест, вы увидите, что c и luke выполняются только тогда, когда выполнение достигает строки

Assert.NotNull(luke)

Если вы используете декомпилятор, такой как JustDecompile, вы увидите, что код модуляпомещается в статический класс с именем Tests$, чей статический конструктор инициализирует свои собственные свойства c и luke.Test async initialization помещается в класс Tests со своими собственными свойствами c и luke, которые делегируются классу Tests$.

Длинная история, ни один из этого кода "инициализации" не выполняется до тех пор, пока не будет запрошено значение luke.Я не знаю, почему это приводит к блокировке теста, скорее всего, есть конфликт с бегуном теста.Достаточно, чтобы код инициализации не запускался при инициализации.

Чтобы код инициализации выполнялся, когда это необходимо, можно использовать «классический» тип теста:

namespace MyTests


open System
open Xunit
open System.Net.Http
open Xunit.Abstractions

type Tests() =

    static let c = new HttpClient (BaseAddress = (Uri "https://swapi.co/api/people/1/"))    

    static let luke = 
        async {                
            return! c.GetStringAsync "" |> Async.AwaitTask        
        } |> Async.RunSynchronously

    static do 
        //Pity we can't actually print here
        printfn "Even more initialization!"

    [<Fact>]
    let ``Test async initialization`` () =  
        Assert.NotNull(luke)

Статическийпривязки в этом случае выполняются перед любым тестом, как и должно, и код не блокируется.Эта инициализация произойдет только один раз.

To захватить вывод конструктор класса тестирования должен принять параметр ITestOutputHelper.Это легко сделать сейчас, когда у нас есть тестовый класс:

type Tests(output:ITestOutputHelper) =

    ...

    [<Fact>]
    let ``Test async initialization`` () =  
        Assert.NotNull(luke)
        output.WriteLine "It worked!"

Инициализация каждого теста должна идти в do блоке:

type Tests(output:ITestOutputHelper) =

    do
      output.WriteLine "This prints before each test"
...