使用xUnit為.net core程序進行單元測試(3)

第1部分:?http://www.cnblogs.com/cgzl/p/8283610.html

第2部分:?http://www.cnblogs.com/cgzl/p/8287588.html

請使用這個項目作為練習的開始:?https://pan.baidu.com/s/1ggcGkGb

測試的分組

打開Game.Tests里面的BossEnemyShould.cs, 為HaveCorrectPower方法添加一個Trait屬性標簽:

        [Fact][Trait("Category", "Enemy")]public void HaveCorrectPower(){BossEnemy sut = new BossEnemy();Assert.Equal(166.667, sut.SpecialAttackPower, 3);}

Trait接受兩個參數, 作為測試分類的Name和Value對.

Build項目, Run All Tests, 然后選擇選擇一下按Traits分組:

這時, Test Explorer里面的tests將會這樣顯示:

再打開EnemyFactoryShould.cs, 為CreateNormalEnemyByDefault方法添加Trait屬性標簽:

        [Fact][Trait("Category", "Enemy")]public void CreateNormalEnemyByDefault(){EnemyFactory sut = new EnemyFactory();Enemy enemy = sut.Create("Zombie");Assert.IsType<NormalEnemy>(enemy);}

Build, 然后查看Test Explorer:

不同的Category:

修改一下BossEnemyShould.cs里面的HaveCorrectPower方法的Trait屬性:

        [Fact][Trait("Category", "Boss")]public void HaveCorrectPower(){BossEnemy sut = new BossEnemy();Assert.Equal(166.667, sut.SpecialAttackPower, 3);}

Build之后, 將會看見兩個分類:

在Class級別進行分類:

只需要把Trait屬性標簽移到Class上面即可:

    [Trait("Category", "Enemy")]public class EnemyFactoryShould{

Build, 查看Test Explorer可以發現EnemyFactoryShould下面所有的Test方法都分類到了Enemy下:

按分類運行測試:?

鼠標右鍵點擊分類, Run Selected Tests就會運行該分類下所有的測試:

按Trait搜索:

在Test Explorer中把分類選擇到Class:

然后在旁邊的Search輸入框中輸入關鍵字, 這時下方會有提示菜單:

點擊Trait, 然后如下圖輸入, 就會把Enemy分類的測試過濾顯示出來:

這種方式同樣也可以進行Trait過濾.

使用命令行進行分類測試

使用命令行進入的Game.Tests, 首先執行命令dotnet test, 這里顯示一共有27個tests:

然后, 可以使用命令:?

dotnet test --filter Category=Enemy

運行分類為Enemy的tests, 結果如圖, 有8個tests:

運行多個分類的tests:

dotnet test --filter "Category=Boss|Category=Enemy"

這句命令會運行分類為Boss或者Enemy的tests, 結果如圖:

共有9個tests.

忽略Test

為Fact屬性標簽設置其Skip屬性, 即可忽略該測試, Skip的值為忽略的原因:

        [Fact(Skip = "不需要跑這個測試")]public void CreateNormalEnemyByDefault_NotTypeExample(){EnemyFactory sut = new EnemyFactory();Enemy enemy = sut.Create("Zombie");Assert.IsNotType<DateTime>(enemy);}

Build, 查看Test Explorer, 選擇按Trait分類顯示, 然后選中Category[Enemy]運行選中的tests:

從這里可以看到, 上面Skip的test被忽略了.

回到命令行, 執行dotnet test:

也可以看到該測試被忽略了, 并且標明了忽略的原因.

打印自定義測試輸出信息:

在test中打印信息需要用到ITestOutputHelper的實現類(注意: 這里使用Console.Writeline是無效的), 在BossEnemyShould.cs里面注入這個helper:

using Xunit;
using Xunit.Abstractions;namespace Game.Tests
{public class BossEnemyShould{private readonly ITestOutputHelper _output;public BossEnemyShould(ITestOutputHelper output){_output = output;}
......

然后在test方法里面這樣寫即可:

        [Fact][Trait("Category", "Boss")]public void HaveCorrectPower(){_output.WriteLine("正在創建 Boss Enemy");BossEnemy sut = new BossEnemy();Assert.Equal(166.667, sut.SpecialAttackPower, 3);}

Build, Run Tests, 這時查看測試結果會發現一個output鏈接:

點擊這個鏈接, 就會顯示測試的輸出信息:

使用命令行:

dotnet test --filter Category=Boss --logger:trx

執行命令后:

可以看到生成了一個TestResults文件夾, 里面是測試的輸出文件, 使用編輯器打開, 它是一個xml文件, 內容如下:

<?xml version="1.0" encoding="UTF-8"?>
<TestRun id="9e552b73-0636-46a2-83d9-c19a5892b3ab" name="solen@DELL-RED 2018-02-10 10:27:19" runUser="DELL-RED\solen" xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2010"><Times creation="2018-02-10T10:27:19.5005784+08:00" queuing="2018-02-10T10:27:19.5005896+08:00" start="2018-02-10T10:27:17.4990291+08:00" finish="2018-02-10T10:27:19.5176327+08:00" /><TestSettings name="default" id="610cad4c-1066-417b-a8e6-d30dce78ef4d"><Deployment runDeploymentRoot="solen_DELL-RED_2018-02-10_10_27_19" /></TestSettings><Results><UnitTestResult executionId="4c6ec739-ccd3-4233-b2bd-8bbde4dfa67f" testId="9e476ed4-3cd9-4f51-aa39-b3d411369979" testName="Game.Tests.BossEnemyShould.HaveCorrectPower" computerName="DELL-RED" duration="00:00:00.0160000" startTime="2018-02-10T10:27:19.2099922+08:00" endTime="2018-02-10T10:27:19.2113656+08:00" testType="13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b" outcome="Passed" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" relativeResultsDirectory="4c6ec739-ccd3-4233-b2bd-8bbde4dfa67f"><Output><StdOut>正在創建 Boss Enemy</StdOut></Output></UnitTestResult></Results><TestDefinitions><UnitTest name="Game.Tests.BossEnemyShould.HaveCorrectPower" storage="c:\users\solen\projects\game\game.tests\bin\debug\netcoreapp2.0\game.tests.dll" id="9e476ed4-3cd9-4f51-aa39-b3d411369979"><Execution id="4c6ec739-ccd3-4233-b2bd-8bbde4dfa67f" /><TestMethod codeBase="C:\Users\solen\projects\Game\Game.Tests\bin\Debug\netcoreapp2.0\Game.Tests.dll" executorUriOfAdapter="executor://xunit/VsTestRunner2/netcoreapp" className="Game.Tests.BossEnemyShould" name="Game.Tests.BossEnemyShould.HaveCorrectPower" /></UnitTest></TestDefinitions><TestEntries><TestEntry testId="9e476ed4-3cd9-4f51-aa39-b3d411369979" executionId="4c6ec739-ccd3-4233-b2bd-8bbde4dfa67f" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" /></TestEntries><TestLists><TestList name="Results Not in a List" id="8c84fa94-04c1-424b-9868-57a2d4851a1d" /><TestList name="All Loaded Results" id="19431567-8539-422a-85d7-44ee4e166bda" /></TestLists><ResultSummary outcome="Completed"><Counters total="1" executed="1" passed="1" failed="0" error="0" timeout="0" aborted="0" inconclusive="0" passedButRunAborted="0" notRunnable="0" notExecuted="0" disconnected="0" warning="0" completed="0" inProgress="0" pending="0" /><Output><StdOut>[xUnit.net 00:00:00.5525795]   Discovering: Game.Tests[xUnit.net 00:00:00.6567207]   Discovered:  Game.Tests[xUnit.net 00:00:00.6755272]   Starting:    Game.Tests[xUnit.net 00:00:00.8743059]   Finished:    Game.Tests</StdOut></Output></ResultSummary>
</TestRun>
View Code

在里面某個Output標簽內可以看到上面寫的測試輸出信息.

減少重復的代碼

xUnit在執行某個測試類的Fact或Theory方法的時候, 都會創建這個類新的實例, 所以有一些公用初始化的代碼可以移動到constructor里面.

打開PlayerCharacterShould.cs, 可以看到每個test方法都執行了new PlayerCharacter()這個動作. 我們應該把這段代碼移動到constructor里面:

namespace Game.Tests
{public class PlayerCharacterShould{private readonly PlayerCharacter _playerCharacter;private readonly ITestOutputHelper _output;public PlayerCharacterShould(ITestOutputHelper output){
       _output = output;
_output.WriteLine("正在創建新的玩家角色");_playerCharacter = new PlayerCharacter();
}[Fact]public void BeInexperiencedWhenNew(){Assert.True(_playerCharacter.IsNoob);}[Fact]public void CalculateFullName(){_playerCharacter.FirstName = "Sarah";_playerCharacter.LastName = "Smith";Assert.Equal("Sarah Smith", _playerCharacter.FullName);
......

Build, Run Tests, 都OK, 并且都有output輸出信息.

除了集中編寫初始化代碼, 也可以集中編寫清理代碼:

這需要該測試類實現IDisposable接口:

?

public class PlayerCharacterShould: IDisposable{......public void Dispose(){_output.WriteLine($"正在清理玩家{_playerCharacter.FullName}");}
}

?

Build, Run Tests, 然后隨便查看一個該類的test的output:

可以看到Dispose()被調用了.

在執行測試的時候共享上下文

上面降到了每個測試方法運行的時候都會創建該測試類新的實例, 可以在constructor里面進行公共的初始化動作.

但是如果初始化的動作消耗資源比較大, 并且時間較長, 那么這種方法就不太好了, 所以下面介紹另外一種方法.

首先在Game項目里面添加類:GameState.cs:

using System;
using System.Collections.Generic;namespace Game
{public class GameState{public static readonly int EarthquakeDamage = 25;public List<PlayerCharacter> Players { get; set; } = new List<PlayerCharacter>();public Guid Id { get; } = Guid.NewGuid();public GameState(){CreateGameWorld();}        public void Earthquake(){foreach (var player in Players){player.TakeDamage(EarthquakeDamage);}}public void Reset(){Players.Clear();}private void CreateGameWorld(){// Simulate expensive creationSystem.Threading.Thread.Sleep(2000);}}
}
View Code

在Game.Tests里面添加類: GameStateShould.cs:

using Xunit;namespace Game.Tests
{public class GameStateShould{[Fact]public void DamageAllPlayersWhenEarthquake(){var sut = new GameState();var player1 = new PlayerCharacter();var player2 = new PlayerCharacter();sut.Players.Add(player1);sut.Players.Add(player2);var expectedHealthAfterEarthquake = player1.Health - GameState.EarthquakeDamage;sut.Earthquake();Assert.Equal(expectedHealthAfterEarthquake, player1.Health);Assert.Equal(expectedHealthAfterEarthquake, player2.Health);}[Fact]public void Reset(){var sut = new GameState();var player1 = new PlayerCharacter();var player2 = new PlayerCharacter();sut.Players.Add(player1);sut.Players.Add(player2);sut.Reset();Assert.Empty(sut.Players);            }}
}
View Code

看一下上面的代碼, 里面有一個Sleep 2秒的動作, 所以執行兩個測試方法的話每個方法都會執行這個動作, 一共用了這些時間:

為了解決這個問題, 我們首先建立一個類 GameStateFixture.cs, 它需要實現IDisposable接口:

using System;namespace Game.Tests
{public class GameStateFixture : IDisposable{public GameState State { get; private set; }public GameStateFixture(){State = new GameState();}public void Dispose(){// Cleanup
        }}
}

然后在GameStateShould類實現IClassFixture接口并帶有泛型的類型:

using Xunit;
using Xunit.Abstractions;namespace Game.Tests
{public class GameStateShould : IClassFixture<GameStateFixture>{private readonly GameStateFixture _gameStateFixture;private readonly ITestOutputHelper _output;public GameStateShould(GameStateFixture gameStateFixture, ITestOutputHelper output){_gameStateFixture = gameStateFixture;_output = output;}[Fact]public void DamageAllPlayersWhenEarthquake(){_output.WriteLine($"GameState Id={_gameStateFixture.State.Id}");var player1 = new PlayerCharacter();var player2 = new PlayerCharacter();_gameStateFixture.State.Players.Add(player1);_gameStateFixture.State.Players.Add(player2);var expectedHealthAfterEarthquake = player1.Health - GameState.EarthquakeDamage;_gameStateFixture.State.Earthquake();Assert.Equal(expectedHealthAfterEarthquake, player1.Health);Assert.Equal(expectedHealthAfterEarthquake, player2.Health);}[Fact]public void Reset(){_output.WriteLine($"GameState Id={_gameStateFixture.State.Id}");var player1 = new PlayerCharacter();var player2 = new PlayerCharacter();_gameStateFixture.State.Players.Add(player1);_gameStateFixture.State.Players.Add(player2);_gameStateFixture.State.Reset();Assert.Empty(_gameStateFixture.State.Players);            }}
}

這個注入的_gameStateFixture在運行多個tests的時候只有一個實例. 所以把消耗資源嚴重的動作放在GameStateFixture里面就可以保證該段代碼只運行一次, 并且被所有的test所共享調用. 要注意的是, 因為上述原因, GameStateFixture里面的代碼不可以有任何副作用, 也就是說可以影響其他的測試結果.

Build, Run Tests:

可以看到運行時間少了很多, 因為那段Sleep代碼只需要運行一次.

再查看一下這個兩個tests的output是一樣的, 也就是說明確實是只生成了一個GameState實例:

在不同的測試類中共享上下文

上面講述了如何在一個測試類中不同的測試里共享代碼的方法, 而xUnit也可以讓我們在不同的測試類中共享上下文.

在Tests項目里建立 GameStateCollection.cs:

using Xunit;namespace Game.Tests
{[CollectionDefinition("GameState collection")]public class GameStateCollection : ICollectionFixture<GameStateFixture> {}
}

這個類GameStateCollection需要實現ICollectionFixture<T>接口, 但是它沒有具體的實現.

它上面的CollectionDefinition屬性標簽作用是定義了一個Collection名字叫做GameStateCollection.?

再建立TestClass1.cs:

using Xunit;
using Xunit.Abstractions;namespace Game.Tests
{[Collection("GameState collection")]public class TestClass1{private readonly GameStateFixture _gameStateFixture;private readonly ITestOutputHelper _output;public TestClass1(GameStateFixture gameStateFixture, ITestOutputHelper output){_gameStateFixture = gameStateFixture;_output = output;}[Fact]public void Test1(){_output.WriteLine($"GameState ID={_gameStateFixture.State.Id}");}[Fact]public void Test2(){_output.WriteLine($"GameState ID={_gameStateFixture.State.Id}");}}
}

和TestClass2.cs:

using Xunit;
using Xunit.Abstractions;namespace Game.Tests
{[Collection("GameState collection")]public class TestClass2{private readonly GameStateFixture _gameStateFixture;private readonly ITestOutputHelper _output;public TestClass2(GameStateFixture gameStateFixture, ITestOutputHelper output){_gameStateFixture = gameStateFixture;_output = output;}[Fact]public void Test3(){_output.WriteLine($"GameState ID={_gameStateFixture.State.Id}");}[Fact]public void Test4(){_output.WriteLine($"GameState ID={_gameStateFixture.State.Id}");}}
}

TestClass1和TestClass2在類的上面使用Collection屬性標簽來調用名為GameState collection的Collection. 而不需要實現任何接口.

這樣, xUnit在運行測試之前會建立一個GameState實例共享與TestClass1和TestClass2.

Build, 同時運行TestClass1和TestClass2的Tests:

運行的時間為3秒多:

查看這4個test的output, 可以看到它們使用的是同一個GameState實例:

這一部分先到這, 還剩下最后一部分了.

?

下面是我的關于ASP.NET Core Web API相關技術的公眾號--草根專欄:

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/284170.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/284170.shtml
英文地址,請注明出處:http://en.pswp.cn/news/284170.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

war部署到tomcat

gs-rest-service-0.1.0.war復制到tomcat-9.0.0.M17\webapps\打開server.xml&#xff0c;這Host節點&#xff0c;加入<Context path"/gs" docBase"gs-rest-service-0.1.0.war" debug"0" privileged"true"/> gs相當于虛擬目錄&…

C# Thread IsBackground作用

背景之前在做一個定時下載任務的時候&#xff0c;使用的是一個主線程在執行任務&#xff1b;后面需求調整了&#xff0c;需要在啟用一個子線程執行優先級更高的單獨通道下載。于是下意識的這么做 new Thread//創建后臺線程Thread bThread new Thread(new ThreadStart(backgrou…

產品經理的分類及術語詳解

一、按項目分類 1、前端型PM 一句話概述&#xff1a;制造口碑帶來流量。 偏用戶體驗&運營&#xff0c;通過極致的產品設計&吸引眼球的產品營銷策略&#xff0c;打造口碑&#xff0c;創造一款用戶量巨大的產品。 【常見術語】 UCD&#xff08;User Centered Design…

Mybatis 攔截器

Mybatis定義了四種攔截器&#xff1a; Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)ParameterHandler (getParameterObject, setParameters)ResultSetHandler (handleResultSets, handleOutputParameters)StatementHandler …

1295 N皇后問題

1295 N皇后問題 時間限制: 2 s 空間限制: 128000 KB 題目等級 : 黃金 Gold 題目描述 Description在nn格的棋盤上放置彼此不受攻擊的n個皇后。按照國際象棋的規則&#xff0c;皇后可以攻擊與之處在同一行或同一列或同一斜線上的棋子。n后問題等價于再nn的棋盤上放置n個皇后&…

CDN的強大功能

2019獨角獸企業重金招聘Python工程師標準>>> CDN&#xff0c;內容分發網絡&#xff0c;除了用作網站加速外&#xff0c;還能夠更好的保護網站不被攻擊。防護網站不被攻擊的功能成就了CDN運行中的主要責任。CDN 防護原理是其主要在于在相關節點中成功的建立動態加速機…

IDEA創建SpringBoot項目無法連接https://start.spring.io(已解決)

錯誤&#xff1a; 方法&#xff1a; 將&#xff1a;https://start.spring.io 更換為 ?https://start.aliyun.com

論人生自動化

就像設備一樣基本都是由三部分組成&#xff0c;輸入&#xff0c;處理&#xff0c;輸出&#xff0c;三部分。當輸出與輸入兩者有比較&#xff0c;自然就產生了反饋&#xff0c;正反饋或者負反饋&#xff0c;有利于輸出的穩定性。有一些東西或者事情能達到閉環&#xff0c;則一切…

MySQL默認數據庫簡介

類似于MS SQL Server等大型數據庫&#xff0c;MySQL數據庫也提供了內置的數據庫&#xff0c;它們是&#xff1a;INFORMATION_SCHEMAmysqltest1.information_schema其中&#xff0c;第一個數據庫INFORMATION_SCHEMA提供了訪問數據庫元數據的方式。元數據是關于數據的數據&#x…

mysql常見監控項

1、MySQL服務運行狀態 約定所有MySQL服務都必須以ip1&#xff08;內網ip&#xff09;來綁定&#xff0c;每個機器只有一個ip1&#xff0c;可以有多個端口&#xff0c;即多個MySQL Server。采集程序讀取ip端口信息文件來判斷server是否存在。 sockParamps aux | grep -P "m…

2005年AMC8數學競賽中英文真題典型考題、考點分析和答案解析

今天距離2024年的AMC8美國數學競賽舉辦已不足一個月了&#xff0c;趕緊利用周末的時間刷刷真題&#xff0c;查漏補缺吧&#xff01;如果您有任何關于AMC8比賽的任何問題都可以問我&#xff0c;關于題目的解析也可以交流。 今天我們來看看2005年AMC8競賽的五道典型考題。歡迎您查…

WPF效果第一百九十三篇之登錄實現

前面一直在玩耍ListBox(最愛),大周末的就適合在家吹著風扇擼著代碼;今天來分享一個很簡單實用的登錄,來看看最終實現的效果:1、關于軟件啟動后焦點實現:<Style TargetType"Border"><Style.Triggers><DataTrigger Binding"{Binding IsEmptyAccoun…

IDEA中安裝并使用JRebel熱部署插件

文章目錄 作者簡介引言導航熱門專欄推薦概述安裝JRebel注冊JRebel配置JRebel最后小結導航熱門專欄推薦作者簡介 作者名&#xff1a;編程界明世隱 簡介&#xff1a;CSDN博客專家&#xff0c;從事軟件開發多年&#xff0c;精通Java、JavaScript&#xff0c;博主也是從零開始一步步…

UWP: 實現 UWP 應用自啟動

原文:UWP: 實現 UWP 應用自啟動在上一篇文章中&#xff0c;我們實現了使用命令行來啟動 UWP 應用&#xff0c;在這一篇文章中&#xff0c;我們會實現 UWP 應用自啟用的實現&#xff0c;也即開機后或用戶登陸后&#xff0c;應用自己啟動。這些特性原來都是 Win32 程序所具備的&a…

選擇 GCD 還是 NSTimer ?

我們常常會延遲某件任務的執行&#xff0c;或者讓某件任務周期性的執行。然后也會在某些時候需要取消掉之前延遲執行的任務。 延遲操作的方案一般有三種&#xff1a; 1.NSObject的方法&#xff1a; 2.使用NSTimer的方法&#xff1a; 3.使用GCD的方法&#xff1a; 一般情況下&am…

完美解決Idea unable to access git 錯誤

在命令行執行 如下命令即可 git config --global --unset http.proxy git config --global --unset https.proxy

Web框架 性能評測 -- C# 的性能 和 Rust、C++并駕齊驅

自從2021年2月第20輪公布的測試以后&#xff0c;一年半后 的2022年7月19日 發布了 TechEmpower 21輪測試報告&#xff1a;Round 21 results - TechEmpower Framework Benchmarks。Techempower benchmark是包含范圍最廣泛的web框架性能測試&#xff0c;覆蓋了比較典型的使用場景…

CF449 C. Jzzhu and Apples

1 /*2 http://codeforces.com/problemset/problem/449/C3 cf 449 C. Jzzhu and Apples4 數論素數貪心5 */6 #include <cstdio>7 #include <algorithm>8 using namespace std;9 const int Nmax100005; 10 int is_prime[Nmax]; 11 int book[Nmax]; 12 int cnt[Nmax];…

【GlobalMapper精品教程】027:路徑剖面和和視線工具的使用

文章目錄 一、路徑剖面簡介二、創建剖面圖1. 加載DEM2. 創建剖面圖3. 計算填挖方3. 保存剖面圖一、路徑剖面簡介 路徑剖面視線工具允許您使用加載的高程數據集沿用戶指定的路徑獲取垂直剖面。 要定義生成3D路徑剖面所遵循的路徑,只需單擊鼠標左鍵選擇路徑的點,然后石鍵單擊…

QT中VideoProbe的簡介和實現

一、遇到問題在Android機上使用QT進行圖像處理程序設計的時候&#xff0c;遇到的一個比較明顯的問題就是圖片采集的問題----攝像頭獲得是實時的視頻&#xff0c;如果我們想從中動態地截獲圖片&#xff0c;并且轉換成Mat的格式&#xff0c;那么僅僅是靜態的imagecapturee就無法完…