首頁 > 軟體

關於Net6 Xunit 整合測試的問題

2022-05-24 14:00:39

對於單元測試、整合測試大部分開發的朋友都懶得去寫,因為這要耗費精力去設計去開發,做完專案模組直接postman 呼叫測試(當然這是一個選擇,開發也中經常用到),但是如果測試需要多樣化資料,各種場景模擬這樣postman測試就暴露了他的侷限性,下面我將Net6下沒有使用Startup以及NET6以前版本使用Startup的整合測試(單元測試雷同)做一個梳理

1.新建測試專案

2.使用到的類庫

Xunti與xunit.runner.visualstudio建立測試專案是自帶

Xunit.DependencyInjection 這是一個測試注入的擴充套件:github地址:https://github.com/pengweiqhca/Xunit.DependencyInjection

Xunit.Extensions.Ordering 這是一個排序執行測試方法的擴充套件,因為有些方法是需要按照順序執行,如獲取圖形驗證碼-->傳送手機驗證碼-->到獲取Token這是一個有序的過程,如果沒有按照順序執行肯定是不對的,github地址:https://github.com/tomaszeman/Xunit.Extensions.Ordering

3.注入

就是將要測試專案的所有注入重新注入測試專案(Program.cs)中和Startup中的所有東東全部注入,NET6中預設取消了Startup類,那麼就要手工全部將這些注入再測試專案中新增一次,注入的時候有些是不相容的做一下小的改動就行,因為https://github.com/pengweiqhca/Xunit.DependencyInjection的注入還是停留在NET5以下版本的,哦對了,還有中介軟體也是需要在測試專案中新增的,

3.1 NET5 以下,測試專案中的Startup需要自己手工建立,區別在於NET5專案有Startup注入的時候不用手動寫很多東西,測試專案直接從專案中的Startup查詢注入,

public class Startup
    {
        // custom host build
        public void ConfigureHost(IHostBuilder hostBuilder)
        {
            hostBuilder
                .ConfigureHostConfiguration(builder =>
                {
                    builder.AddJsonFile("appsettings.json", true);
                })
                .ConfigureWebHostDefaults(builder =>
                {
                    builder.UseStartup<Dx.H5.Service.Startup>();//此處為專案中的startup,不是測試專案中的startup
                    builder.UseTestServer();
                    builder.ConfigureServices(services =>
                    {
                        services.AddSingleton(sp => sp.GetRequiredService<IHost>()
                            .GetTestClient()
                        );
                    });
                })
                ;
        }
        // add services need to injection
        // ConfigureServices(IServiceCollection services)
        // ConfigureServices(IServiceCollection services, HostBuilderContext hostBuilderContext)
        // ConfigureServices(HostBuilderContext hostBuilderContext, IServiceCollection services)
        public void ConfigureServices(IServiceCollection services)
        {
            // ready check
            //services.AddHostedService<ReadyCheckHostedService>();
        }
        public void Configure(ILoggerFactory loggerFactory, ITestOutputHelperAccessor outputHelperAccessor)
        {
            loggerFactory.AddProvider(new XunitTestOutputLoggerProvider(outputHelperAccessor));
        }
    }

3.2 NET6 ,測試專案中的Startup需要自己手工建立,NET6 專案取消了Startup那麼就需要手工搬移所有的注入,需要注意的是Startup中的ConfigureServices不支援過載,也就是你只能用一個ConfigureServices方法,見如下範例,還有測試紀錄檔的注入,但是在測試專案中使用ILogger<>好像是有問題的,有時紀錄檔不列印, 使用private readonly ITestOutputHelperAccessor _testOutputHelperAccessor; 替代ILogger<>,直接在測試類建構函式中注入就行

public class Startup
    {
        const string DefaultCorsPolicyName = "Default";
        public void ConfigureHost(IHostBuilder hostBuilder) =>
            hostBuilder.ConfigureWebHost(webHostBuilder => webHostBuilder
                .UseTestServer()
                .Configure(Configure)
                .UseUrls("http://*:17890","http://*:17880")
                .ConfigureServices(services =>
                {
                    services.AddSingleton(sp => sp.GetRequiredService<IHost>()
                            .GetTestClient()
                        );
                })
            )
            .ConfigureAppConfiguration(lb => lb.AddJsonFile("appsettings.json", false, true));
        public void ConfigureServices(IServiceCollection services, HostBuilderContext context)
        {
            services.AddControllers();
            services.AddEndpointsApiExplorer();
            services.AddSwaggerGen();
            services.AddHttpClient();
        }
        public void Configure(ILoggerFactory loggerFactory, ITestOutputHelperAccessor accessor) =>
        loggerFactory.AddProvider(new XunitTestOutputLoggerProvider(accessor));
        ////public void ConfigureHost(IHostBuilder hostBuilder) { }
        //public void ConfigureServices(IServiceCollection services)
        //{
        //}
        //public IHostBuilder CreateHostBuilder(AssemblyName assemblyName) 
        //{
        //    return new HostBuilder();
        //}
        private void Configure(IApplicationBuilder app)
        {
            //if (app.Environment.IsDevelopment())
            //{
            //    app.UseSwagger();
            //    app.UseSwaggerUI();
            //}
            app.UseRouting();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints
                .MapControllers();
                endpoints.MapGet("/hb/generatetoken", context =>
                {
                    return context.Response.WriteAsync(GenerateToken(context));
                });
            });
        }
        string GenerateToken(HttpContext httpContext)
        {
            return "dfdfdfdfd";
        }
    }
public static IEnumerable<object?[]> ReadFile()
        {
            yield return new object[] { "123"};
            yield return new object[] { "456" };
        }

4.建立測試類測試方法:

需要注意的是api介面測試中url忽略host與埠,預設埠設定請查閱https://github.com/pengweiqhca/Xunit.DependencyInjection檔案,UnitTest2中的測試方法是帶有資料集合的測試方法,及測試方法是執行多次的,測試方法中的引數資料就是由MemberData(nameof(ReadFile)),其中資料方法ReadFile必須是 public staticReadFile要不然會有報錯

using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using Xunit;
using Xunit.DependencyInjection;
using Xunit.Extensions.Ordering;
using static WebApiXunit.Controllers.WeatherForecastController;
namespace Xunit.WebApiTest
{
    [Order(1)]
    public class UnitTest1
    {
        private HttpClient _client;
        private ILogger<UnitTest1> _logger;
        //private readonly ITestOutputHelperAccessor _testOutputHelperAccessor;
        public UnitTest1(
            //ITestOutputHelperAccessor testOutputHelperAccessor
            ILogger<UnitTest1> logger,
            HttpClient client
            )
        {
            _logger = logger;
            _client = client;
        }
        [Order(1)]
        [Fact(DisplayName = "1")]
        public async Task Test1()
        {
            var c = new MyClass();
            c.Name = "1";
            c.Description = "e";
            using var request = new HttpRequestMessage(HttpMethod.Post, "WeatherForecast/hb/post/add");
            var content = JsonConvert.SerializeObject(c);
            request.Content = new StringContent(content);
            request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
            var resp = await _client.SendAsync(request);
            //var resp=await _client.GetAsync("/hb/generatetoken");
            _logger.LogInformation("成功");
            if (resp.IsSuccessStatusCode)
            {
                var str = await resp.Content.ReadAsStringAsync();
                Assert.True(true);
                return;
            }
            Assert.True(false);
        }
        [Order(2)]
        [Fact(DisplayName = "2")]
        public async Task Test2()
        {
            var c = new MyClass();
            c.Name = "1";
            c.Description = "e";
            using var request = new HttpRequestMessage(HttpMethod.Post, "WeatherForecast/hb/post/add");
            var content = JsonConvert.SerializeObject(c);
            request.Content = new StringContent(content);
            request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
            var resp = await _client.SendAsync(request);
            //var resp=await _client.GetAsync("/hb/generatetoken");
            //_testOutputHelperAccessor.Output.WriteLine("");
            _logger.LogInformation("成功");
            if (resp.IsSuccessStatusCode)
            {
                var str = await resp.Content.ReadAsStringAsync();
                Assert.True(true);
                return;
            }
            Assert.True(false);
        }
    }
    [Order(2)]
    public class UnitTest2
    {
        private HttpClient _client;
        private ILogger<UnitTest2> _logger;
        public UnitTest2(
            ILogger<UnitTest2> logger,
            HttpClient client
            )
        {
            _logger = logger;
            _client = client;
        }
        [Theory]
        [MemberData(nameof(ReadFile))]
        public async Task Test2(string name)
        {
            _logger.LogInformation(name);
            Assert.True(true);
        }
        public static IEnumerable<object?[]> ReadFile()
        {
            yield return new object[] { "123"};
            yield return new object[] { "456" };
        }
    }
}

5.排序執行測試方法:

使用Xunit.Extensions.Ordering進行排序執行測試方法時:首先在測試專案中新建一個AssemblyInfo.cs加入如下內容,主要沒有類名及名稱空間,其中[assembly: TestFramework("Xunit.Extensions.Ordering.TestFramework", "Xunit.Extensions.Ordering")]是一個按照集合進行排序的使用,但是他與已有xunit assembly衝突,暫時麼有找到解決方法,所以該排序功能暫時不支援,類中的[Order(2)]為第一優先順序排序順序,方法中的[Order(2)]即在類的順序下再排序

using Xunit;
using Xunit.Extensions.Ordering;
[assembly: CollectionBehavior(DisableTestParallelization = true)]
//[assembly: TestFramework("Xunit.Extensions.Ordering.TestFramework", "Xunit.Extensions.Ordering")]
[assembly: TestCaseOrderer("Xunit.Extensions.Ordering.TestCaseOrderer", "Xunit.Extensions.Ordering")]
[assembly: TestCollectionOrderer("Xunit.Extensions.Ordering.CollectionOrderer", "Xunit.Extensions.Ordering")]

using Xunit;
using Xunit.Extensions.Ordering;
[assembly: CollectionBehavior(DisableTestParallelization = true)]
//[assembly: TestFramework("Xunit.Extensions.Ordering.TestFramework", "Xunit.Extensions.Ordering")]
[assembly: TestCaseOrderer("Xunit.Extensions.Ordering.TestCaseOrderer", "Xunit.Extensions.Ordering")]
[assembly: TestCollectionOrderer("Xunit.Extensions.Ordering.CollectionOrderer", "Xunit.Extensions.Ordering")]

6.執行測試專案:

在vs中執行測試專案中右鍵可以看到執行和偵錯測試專案執行比較簡單,如果在伺服器上需要使用dotnet test執行測試,注意組態檔要與伺服器的匹配,將專案整體目錄拷貝到伺服器,cd 到測試專案目錄下執行 dotnet test,有多少個介面瞬間測試完畢,而且在專案後續迭代更新的時候,只需要執行以下就可以測試所有的介面。

到此這篇關於Net6 Xunit 整合測試的文章就介紹到這了,更多相關Net6 Xunit 整合測試內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com