欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Asp.Net Core利用xUnit進(jìn)行主機(jī)級(jí)別的網(wǎng)絡(luò)集成測(cè)試詳解

 更新時(shí)間:2018年12月11日 10:24:44   作者:Ron.liang  
這篇文章主要給大家介紹了關(guān)于Asp.Net Core利用xUnit進(jìn)行主機(jī)級(jí)別的網(wǎng)絡(luò)集成測(cè)試的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們來(lái)一起看看吧

前言

在開(kāi)發(fā) Asp.Net Core 應(yīng)用程序的過(guò)程中,我們常常需要對(duì)業(yè)務(wù)代碼編寫單元測(cè)試,這種方法既快速又有效,利用單元測(cè)試做代碼覆蓋測(cè)試,也是非常必要的事情;但是,但我們需要對(duì)系統(tǒng)進(jìn)行集成測(cè)試的時(shí)候,需要啟動(dòng)服務(wù)主機(jī),利用瀏覽器或者Postman 等網(wǎng)絡(luò)工具對(duì)接口進(jìn)行集成測(cè)試,這就非常的不方便,同時(shí)浪費(fèi)了大量的時(shí)間在重復(fù)啟動(dòng)應(yīng)用程序上;今天要介紹就是如何在不啟動(dòng)應(yīng)用程序的情況下,對(duì) Asp.Net Core WebApi 項(xiàng)目進(jìn)行網(wǎng)絡(luò)集成測(cè)試。

一、建立項(xiàng)目

1.1 首先我們建立兩個(gè)項(xiàng)目,Asp.Net Core WebApi 和 xUnit 單元測(cè)試項(xiàng)目,如下

1.2 上圖的單元測(cè)試項(xiàng)目 Ron.XUnitTest 必須應(yīng)用待測(cè)試的 WebApi 項(xiàng)目 Ron.TestDemo

1.3 接下來(lái)打開(kāi) Ron.XUnitTest 項(xiàng)目文件 .csproj,添加包引用

Microsoft.AspNetCore.App
Microsoft.AspNetCore.TestHost

1.4 為什么要引用這兩個(gè)包呢,因?yàn)槲覄偛艅?chuàng)建的 WebApi 項(xiàng)目是引用 Microsoft.AspNetCore.App 的,至于 Microsoft.AspNetCore.TestHost,它是今天的主角,為了使用測(cè)試主機(jī),必須對(duì)其進(jìn)行引用,下面會(huì)詳細(xì)說(shuō)明

二、編寫業(yè)務(wù)

2.1 創(chuàng)建一個(gè)接口,代碼如下

 [Route("api/[controller]")]
 [ApiController]
 public class ValuesController : ControllerBase
 {
 private IConfiguration configuration;
 public ValuesController(IConfiguration configuration)
 {
  this.configuration = configuration;
 }

 [HttpGet("{id}")]
 public ActionResult<int> Get(int id)
 {
  var result= id + this.configuration.GetValue<int>("max");

  return result;
 }
 }

2.1 接口代碼非常簡(jiǎn)單,接受一個(gè)參數(shù) id,然后和配置文件中獲取的值 max 相加,然后輸出結(jié)果給客戶端

三、編寫測(cè)試用例

3.1 為了能夠使用主機(jī)集成測(cè)試,我們需要使用類

Microsoft.AspNetCore.TestHost.TestServer

3.2 我們來(lái)看一下 TestServer 的源碼,代碼較長(zhǎng),你可以直接跳過(guò)此段,進(jìn)入下一節(jié) 3.3

 public class TestServer : IServer
 {
 private IWebHost _hostInstance;
 private bool _disposed = false;
 private IHttpApplication<Context> _application;

 public TestServer(): this(new FeatureCollection())
 {
 }

 public TestServer(IFeatureCollection featureCollection)
 {
  Features = featureCollection ?? throw new ArgumentNullException(nameof(featureCollection));
 }

 public TestServer(IWebHostBuilder builder): this(builder, new FeatureCollection())
 {
 }
 
 public TestServer(IWebHostBuilder builder, IFeatureCollection featureCollection): this(featureCollection)
 {
  if (builder == null)
  {
  throw new ArgumentNullException(nameof(builder));
  }

  var host = builder.UseServer(this).Build();
  host.StartAsync().GetAwaiter().GetResult();
  _hostInstance = host;
 }

 public Uri BaseAddress { get; set; } = new Uri("http://localhost/");

 public IWebHost Host
 {
  get
  {
  return _hostInstance
   ?? throw new InvalidOperationException("The TestServer constructor was not called with a IWebHostBuilder so IWebHost is not available.");
  }
 }

 public IFeatureCollection Features { get; }

 private IHttpApplication<Context> Application
 {
  get => _application ?? throw new InvalidOperationException("The server has not been started or no web application was configured.");
 }

 public HttpMessageHandler CreateHandler()
 {
  var pathBase = BaseAddress == null ? PathString.Empty : PathString.FromUriComponent(BaseAddress);
  return new ClientHandler(pathBase, Application);
 }

 public HttpClient CreateClient()
 {
  return new HttpClient(CreateHandler()) { BaseAddress = BaseAddress };
 }

 public WebSocketClient CreateWebSocketClient()
 {
  var pathBase = BaseAddress == null ? PathString.Empty : PathString.FromUriComponent(BaseAddress);
  return new WebSocketClient(pathBase, Application);
 }

 public RequestBuilder CreateRequest(string path)
 {
  return new RequestBuilder(this, path);
 }

 public async Task<HttpContext> SendAsync(Action<HttpContext> configureContext, CancellationToken cancellationToken = default)
 {
  if (configureContext == null)
  {
  throw new ArgumentNullException(nameof(configureContext));
  }

  var builder = new HttpContextBuilder(Application);
  builder.Configure(context =>
  {
  var request = context.Request;
  request.Scheme = BaseAddress.Scheme;
  request.Host = HostString.FromUriComponent(BaseAddress);
  if (BaseAddress.IsDefaultPort)
  {
   request.Host = new HostString(request.Host.Host);
  }
  var pathBase = PathString.FromUriComponent(BaseAddress);
  if (pathBase.HasValue && pathBase.Value.EndsWith("/"))
  {
   pathBase = new PathString(pathBase.Value.Substring(0, pathBase.Value.Length - 1));
  }
  request.PathBase = pathBase;
  });
  builder.Configure(configureContext);
  return await builder.SendAsync(cancellationToken).ConfigureAwait(false);
 }

 public void Dispose()
 {
  if (!_disposed)
  {
  _disposed = true;
  _hostInstance.Dispose();
  }
 }

 Task IServer.StartAsync<TContext>(IHttpApplication<TContext> application, CancellationToken cancellationToken)
 {
  _application = new ApplicationWrapper<Context>((IHttpApplication<Context>)application, () =>
  {
  if (_disposed)
  {
   throw new ObjectDisposedException(GetType().FullName);
  }
  });

  return Task.CompletedTask;
 }

 Task IServer.StopAsync(CancellationToken cancellationToken)
 {
  return Task.CompletedTask;
 }

 private class ApplicationWrapper<TContext> : IHttpApplication<TContext>
 {
  private readonly IHttpApplication<TContext> _application;
  private readonly Action _preProcessRequestAsync;

  public ApplicationWrapper(IHttpApplication<TContext> application, Action preProcessRequestAsync)
  {
  _application = application;
  _preProcessRequestAsync = preProcessRequestAsync;
  }

  public TContext CreateContext(IFeatureCollection contextFeatures)
  {
  return _application.CreateContext(contextFeatures);
  }

  public void DisposeContext(TContext context, Exception exception)
  {
  _application.DisposeContext(context, exception);
  }

  public Task ProcessRequestAsync(TContext context)
  {
  _preProcessRequestAsync();
  return _application.ProcessRequestAsync(context);
  }
 }
 }

3.3 TestServer 類代碼量比較大,不過(guò)不要緊,我們只需要關(guān)注它的構(gòu)造方法就可以了

 public TestServer(IWebHostBuilder builder)
  : this(builder, new FeatureCollection())
 {
 }

3.4 其構(gòu)造方法接受一個(gè) IWebHostBuilder 對(duì)象,只要我們傳入一個(gè) WebHostBuilder 就可以創(chuàng)建一個(gè)測(cè)試主機(jī)了

3.5 創(chuàng)建測(cè)試主機(jī)和 HttpClient 客戶端,我們?cè)跍y(cè)試類 ValuesUnitTest 編寫如下代碼

 public class ValuesUnitTest
 {
 private TestServer testServer;
 private HttpClient httpCLient;

 public ValuesUnitTest()
 {
  testServer = new TestServer(new WebHostBuilder().UseStartup<Ron.TestDemo.Startup>());
  httpCLient = testServer.CreateClient();
 }

 [Fact]
 public async void GetTest()
 {
  var data = await httpCLient.GetAsync("/api/values/100");
  var result = await data.Content.ReadAsStringAsync();

  Assert.Equal("300", result);
 }
 }

代碼解釋

這段代碼非常簡(jiǎn)單,首先,我們聲明了一個(gè) TestServer 和 HttpClient 對(duì)象,并在構(gòu)造方法中初始化他們; TestServer 的初始化是由我們 new 了一個(gè) Builder 對(duì)象,并指定其使用待測(cè)試項(xiàng)目 Ron.TestDemo 中的 Startup 類來(lái)啟動(dòng),這樣我們能可以直接使用待測(cè)試項(xiàng)目的路由和管道了,甚至我們無(wú)需指定測(cè)試站點(diǎn),因?yàn)檫@些都會(huì)在 TestServer 自動(dòng)配置一個(gè) localhost 的主機(jī)地址

3.7 接下來(lái)就是創(chuàng)建了一個(gè)單元測(cè)試的方法,直接使用剛才初始化的 HttpClient 對(duì)象進(jìn)行網(wǎng)絡(luò)請(qǐng)求,這個(gè)時(shí)候,我們只需要知道 Action 即可,同時(shí)傳遞參數(shù) 100,最后斷言服務(wù)器輸出值為:"300",回顧一下我們創(chuàng)建的待測(cè)試方法,其業(yè)務(wù)正是將客戶端傳入的 id 值和配置文件 max 值相加后輸出,而 max 值在這里被配置為 200

3.8 運(yùn)行單元測(cè)試

3.9 測(cè)試通過(guò),可以看到,測(cè)試達(dá)到了預(yù)期的結(jié)果,服務(wù)器正確返回了計(jì)算后的值

四、配置文件注意事項(xiàng)

4.1 在待測(cè)試項(xiàng)目中的配置文件 appsettings.json 并不會(huì)被測(cè)試主機(jī)所讀取,因?yàn)槲覀冊(cè)谏厦鎰?chuàng)建測(cè)試主機(jī)的時(shí)候沒(méi)有調(diào)用方法

WebHost.CreateDefaultBuilder

4.2 我們只是創(chuàng)建了一個(gè) WebHostBuilder 對(duì)象,非常輕量的主機(jī)配置,簡(jiǎn)單來(lái)說(shuō)就是無(wú)配置,如果對(duì)于 WebHost.CreateDefaultBuilder 不理解的同學(xué),建議閱讀我的文章 asp.netcore 深入了解配置文件加載過(guò)程.

4.3 所以,為了能夠在單元測(cè)試中使用項(xiàng)目配置文件,我在 Ron.TestDemo 項(xiàng)目中的 Startup 類加入了下面的代碼

 public class Startup
 {
 public Startup(IConfiguration configuration, IHostingEnvironment env)
 {
  this.Configuration = new ConfigurationBuilder()
  .AddJsonFile("appsettings.json")
  .AddEnvironmentVariables()
  .SetBasePath(env.ContentRootPath)
  .Build();
 }

 public IConfiguration Configuration { get; }

 // This method gets called by the runtime. Use this method to add services to the container.
 public void ConfigureServices(IServiceCollection services)
 {
  services.AddSingleton<IConfiguration>(this.Configuration);
  services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
 }
 }

4.4 其目的就是手動(dòng)讀取配置文件,重新初始化 IConfiguration 對(duì)象,并將 this.Configuration 對(duì)象加入依賴注入容器中

結(jié)語(yǔ)

  • 本文從單元測(cè)試入手,針對(duì)常見(jiàn)的系統(tǒng)集成測(cè)試提供了另外一種便捷的測(cè)試方案,通過(guò)創(chuàng)建 TestServer 測(cè)試主機(jī)開(kāi)始,利用主機(jī)創(chuàng)建 HttpCLient 對(duì)象進(jìn)行網(wǎng)絡(luò)集成測(cè)試
  • 減少重復(fù)啟動(dòng)程序和測(cè)試工具,提高了測(cè)試效率
  • 充分利用了 Visual Studio 的優(yōu)勢(shì),既可以做單元測(cè)試,還能利用這種測(cè)試方案進(jìn)行快速代碼調(diào)試
  • 最后,還了解如何通過(guò) TestServer 主機(jī)加載待測(cè)試項(xiàng)目的配置文件對(duì)象 IConfiguration

示例代碼下載

http://xiazai.jb51.net/201812/yuanma/Ron.TestDemo_jb51.rar

總結(jié)

以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。

相關(guān)文章

  • 使用HttpClient增刪改查ASP.NET Web API服務(wù)

    使用HttpClient增刪改查ASP.NET Web API服務(wù)

    這篇文章介紹了使用HttpClient增刪改查ASP.NET Web API服務(wù)的方法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-10-10
  • 禁止ViewState的3種解決方法

    禁止ViewState的3種解決方法

    默認(rèn)情況下,ViewState是被啟用的,比如提交表單后,表單中輸入的值會(huì)自動(dòng)保留。但是如果不需要保留,也可以將其禁用,這樣可以節(jié)省資源。
    2013-03-03
  • .Net基于Thread實(shí)現(xiàn)自旋鎖的三種方式

    .Net基于Thread實(shí)現(xiàn)自旋鎖的三種方式

    本文主要講解.Net基于Thread實(shí)現(xiàn)自旋鎖的三種方式,基于Test--And--Set原子操作實(shí)現(xiàn),包含優(yōu)缺點(diǎn)介紹,感興趣的朋友跟隨小編一起看看吧
    2021-06-06
  • 如何利用HttpClientFactory實(shí)現(xiàn)簡(jiǎn)單的熔斷降級(jí)

    如何利用HttpClientFactory實(shí)現(xiàn)簡(jiǎn)單的熔斷降級(jí)

    這篇文章主要給大家介紹了關(guān)于如何利用HttpClientFactory實(shí)現(xiàn)簡(jiǎn)單的熔斷降級(jí)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2018-07-07
  • C#中使用SQLite數(shù)據(jù)庫(kù)的方法介紹

    C#中使用SQLite數(shù)據(jù)庫(kù)的方法介紹

    SQLite是一個(gè)開(kāi)源的輕量級(jí)的桌面型數(shù)據(jù)庫(kù),它將幾乎所有數(shù)據(jù)庫(kù)要素(包括定義、表、索引和數(shù)據(jù)本身)都保存在一個(gè)單一的文件中。SQLite用C編寫實(shí)現(xiàn),它在內(nèi)存消耗、文件體積、操作性能、簡(jiǎn)單性方面都有不錯(cuò)的表現(xiàn)
    2012-01-01
  • ASP.NET Global.asax應(yīng)用程序文件簡(jiǎn)介

    ASP.NET Global.asax應(yīng)用程序文件簡(jiǎn)介

    Global.asax 文件,有時(shí)候叫做 ASP.NET 應(yīng)用程序文件,提供了一種在一個(gè)中心位置響應(yīng)應(yīng)用程序級(jí)或模塊級(jí)事件的方法。
    2009-03-03
  • asp.net(c#) RSS功能實(shí)現(xiàn)代碼

    asp.net(c#) RSS功能實(shí)現(xiàn)代碼

    這兩天一邊從網(wǎng)上找資料,自己再測(cè)試,終于完成本站的RSS功能了!先自我恭喜下!!  
    2008-11-11
  • ASP.NET 4.0配置文件中的ClientIDMode屬性詳解

    ASP.NET 4.0配置文件中的ClientIDMode屬性詳解

    在ASP.NET 4.0中的每個(gè)控件上都多了一個(gè)叫做ClientIDMode的屬性,本文主要介紹了ASP.NET 4.0配置文件中的ClientIDMode屬性詳解,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2018-11-11
  • asp.net coolite 刪除時(shí)彈出確定按鈕

    asp.net coolite 刪除時(shí)彈出確定按鈕

    如果用coolite的 Confirm() 是不知道你選擇了什么的 如上代碼才可以的
    2009-09-09
  • asp.net mvc CodeFirst模式數(shù)據(jù)庫(kù)遷移步驟詳解

    asp.net mvc CodeFirst模式數(shù)據(jù)庫(kù)遷移步驟詳解

    這篇文章主要為大家詳細(xì)介紹了asp.net mvc CodeFirst模式數(shù)據(jù)庫(kù)遷移步驟,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-10-10

最新評(píng)論