系列目錄html
經過前面兩節講解,咱們的測試類中已經有兩個測試方法了,整體上以下web
public class mvc20 { private readonly HttpClient _client; public mvc20() { var builder = new WebHostBuilder() .UseContentRoot(@"E:\personal project\newTest2018\ConsoleApp1\CoreMvc") .UseEnvironment("Development") .UseStartup<CoreMvc.Startup>(); var server = new TestServer(builder); _client = server.CreateClient(); } [Fact] public async Task SimpleGet() { var response = await _client.GetAsync("/HelloWorld/Hello"); response.EnsureSuccessStatusCode(); var responseStr = await response.Content.ReadAsStringAsync(); Assert.Equal("Hello,World", responseStr); } [Theory] [AutoData] public async Task SimplePost(Student stud) { var content = new StringContent(JsonConvert.SerializeObject(stud), Encoding.UTF8, "application/json"); var response = await _client.PostAsync("/HelloWorld/StudentInfo", content); response.EnsureSuccessStatusCode(); var result = await response.Content.ReadAsStringAsync(); Assert.True(!string.IsNullOrEmpty(result)); } }
以上方法看似沒有問題,實際上卻有一個性能陷阱,咱們經過前面章節的知識已經知道,xunit裏測試類的構造函數會在每個測試方法運行的時候都執行一遍,一般狀況下咱們的測試代碼遠不止三幾個,有時候幾十個甚至上百個.這樣每次都建立一個是很是影響性能的.而且這裏的TestServer和_client都沒有釋放.此外就是web項目裏可能每個測試類都須要建立這樣一個TestServer,這樣重複的代碼會複製不少次,帶來維護困難.sql
咱們前面講到過,咱們若是想要讓一個對象在一個測試類中只初始化一次,就要讓這個類實現IClassFixture泛型接口,類在初始化的時候會自動注入這個泛型對象的實體,而且只初始化一次,若是這個泛型對象實現了IDisposable接口,則會在測試類全部方法都執行完成的時候執行這個對象裏的Dispose方法.json
首先咱們建立一個名爲MyTestServerFixtrue
的類,TestServer和HttpClient對象的初始化在這裏執行.代碼以下服務器
public class MyTestServerFixtrue:IDisposable { public readonly HttpClient _client; private readonly TestServer _server; public MyTestServerFixtrue() { var builder = new WebHostBuilder() .UseContentRoot(@"E:\personal project\newTest2018\ConsoleApp1\CoreMvc") .UseEnvironment("Development") .UseStartup<CoreMvc.Startup>(); _server = new TestServer(builder); _client = _server.CreateClient(); } public void Dispose() { _client.Dispose(); _server.Dispose(); }
這裏的方法和參數大部分都和前面在測試類中添加的同樣,只是有如下幾點須要注意:
1.把server變量放在構造函數外邊,這樣咱們才能在Dispose裏把它釋放掉,否則沒法定位到它.
2.把client變成public類型,由於咱們須要在測試類中訪問它.mvc
下面咱們再看測試類改造後的代碼app
public class mvc20:IClassFixture<MyTestServerFixtrue> { private readonly HttpClient _client; public mvc20(MyTestServerFixtrue fixtrue) { this._client = fixtrue._client; } }
這裏是主要代碼,首先這個實現了IClassFixture,而後咱們把無參構造函數改變成有參的,而且傳入MyTestServerFixtrue類型對象,Xunit會自動注入這個對象,而後咱們把這個對象裏的httpclient賦值給本類的_client對象,這樣咱們就能夠在本類中使用它了.異步
這樣其它的測試類也能夠實現IClassFixture<MyTestServerFixtrue>
,若是想要改TestServer的配置只須要在MyTestServerFixtrue類中改就好了.async
咱們看到前面講到的兩個測試方法提交的路徑中都包含"/HelloWorld",它其實匹配控制器名,通常狀況下同一個Controller下的方法的測試方法都寫在同一個測試類中.這樣Controller名稱是固定的,咱們能夠把它單獨抽離出來,只須要Action後面的路由.函數
咱們把測試類改爲以下:
public class mvc20:IClassFixture<MyTestServerFixtrue> { private readonly HttpClient _client; public mvc20(MyTestServerFixtrue fixtrue) { var baseAddr = fixtrue._client.BaseAddress.AbsoluteUri; string controllerName ="HelloWorld"; this._client = fixtrue._client; if (!fixtrue._client.BaseAddress.AbsoluteUri.Contains(controllerName)) { fixtrue._client.BaseAddress = new Uri(baseAddr + controllerName+"/"); } } [Fact] public async Task SimpleGet() { var response = await _client.GetAsync($"{nameof(HelloWorldController.Hello)}"); response.EnsureSuccessStatusCode(); var responseStr = await response.Content.ReadAsStringAsync(); Assert.Equal("Hello,World", responseStr); } [Theory] [AutoData] public async Task SimplePost(Student stud) { var content = new StringContent(JsonConvert.SerializeObject(stud), Encoding.UTF8, "application/json"); var response = await _client.PostAsync($"{nameof(HelloWorldController.StudentInfo)}", content); response.EnsureSuccessStatusCode(); var result = await response.Content.ReadAsStringAsync(); Assert.True(!string.IsNullOrEmpty(result)); } }
這裏咱們把controller的名稱加到HttpClient的BaseUrl裏面,而後發送get,post等請求的時候只要Action的名字,這裏咱們使用nameof關鍵字來獲取action的名字,使用nameof關鍵字來獲取的好處是:第一,咱們點擊方法名就能夠快速定位到指定的方法.更爲重要的是若是方法的名稱改了,編譯的時候就會出現編譯錯誤,咱們能夠快速定位到錯誤而後修改.
上面MyTestServerFixtrue類中的代碼有一處有明顯問題:那就是UseContentRoot裏的路徑是寫死的,項目在本機上地址與在服務器上的或者與其它同事的絕大多數狀況下是不同的(由於你們項目所在的目錄名不相同)這時候若是其它人調用這些代碼就可能會出現錯誤.
咱們可使用相對路徑來獲取絕對路來解決這個問題,因爲這兩個項目的主文件夾在同一文件夾下面,所以測試項目向外退若干層就可以獲得mvc項目的主目錄了.
咱們將MyTestServerFixtrue
類的構造方法改成以下:
public MyTestServerFixtrue() { var rootPath = GetContentRootDir(); var builder = new WebHostBuilder() .UseContentRoot(rootPath) .UseEnvironment("Development") .UseStartup<CoreMvc.Startup>(); _server = new TestServer(builder); _client = _server.CreateClient(); }
此次咱們不是再寫死rootPath而是經過方法GetContentRootDir
來獲取.
下面咱們來看這個GetContentRootDir
方法
private string GetContentRootDir() { var currentPath = AppDomain.CurrentDomain.BaseDirectory; var relativePath = @"..\..\..\..\CoreMvc"; var combinedPath = Path.Combine(currentPath, relativePath); var absPath = Path.GetFullPath(combinedPath); return absPath; }
首先咱們先獲取當前程序域的目錄,也就是程序的運行目錄,獲取到它以後咱們看看向上移動多少層可以到達包含mvc項目和這個test項目的文件夾,經查是四層,下面的相對路徑咱們就寫爲如變量relativePath
定義的那樣.
咱們把它們組合在一塊兒,而後經過Path.GetFullPath來獲取到相對路徑的絕路徑.
有時候服務器故障會致使請求很是慢,服務器很長時間沒法返回請求,這就會致使集成測試代碼一直'卡'着沒法完成,這時候能夠設置一個超時.設置很是簡單,HttpClient有一個Timeout屬性,設置相應的超時時間便可.HttpClient的默認請求超時時間是100s,這個值應該大部分時候不須要修改的,可是關於具體的業務,可能有一些方法自己執行時間特別長(業務邏輯很是複雜,sql語句很是複雜等)這時候能夠單元給本次請求設置一個超時時間.好比說是150s,設置以下
CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(150)); var response = await client.GetAsync("/Home/index", cts.Token);
這裏定義一個CancellationTokenSource對象,並指定超時時間,而後把此對象的Token對象傳給異步請求方法.