1. 程式人生 > >使用xUnit為.net core程式進行單元測試(3)

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

測試的分組

開啟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="[email protected] 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 creation
            System.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例項:

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

相關推薦

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

測試的分組 開啟Game.Tests裡面的BossEnemyShould.cs, 為HaveCorrectPower方法新增一個Trait屬性標籤: [Fact] [Trait("Category", "Enemy")] public v

使用xUnit.net core程式進行單元測試(4)

資料驅動的測試 開啟PlayerCharacterShould.cs 新增幾個Fact測試方法: [Fact] public void TakeZeroDamage() { _sut.TakeDamage(

使用xUnit.net core程式進行單元測試 -- Assert

Assert Assert做什麼?Assert基於程式碼的返回值、物件的最終狀態、事件是否發生等情況來評估測試的結果。Assert的結果可能是Pass或者Fail。如果所有的asserts都pass了,那麼整個測試就pass了;如果有任何assert fail了,那麼測試就fail了。 xUnit提供

使用xUnit.net core程式進行單元測試(1)

導讀 為什麼要編寫自動化測試程式(Automated Tests)? 可以頻繁的進行測試 可以在任何時間進行測試,也可以按計劃定時進行,例如:可以在半夜進行自動測試。 肯定比人工測試要快。 可以更快速的發現錯誤。 基本上是非常可靠的。 測試程式碼與生產程式碼緊密結合。 使得開發團隊更具有幸

.NET Core: 在.NET Core進行單元測試

單元測試能夠幫助開發人員確保所開發的模組、類以及類中的方法等的正確性,在專案開發過程中,及時進行單元測試能夠避免不必要的BUG以及提高測試效率。 在本文中,我們會分別來學習如何使用MSTest、xUnit以及NUnit這些流行的.NET測試框架來對.NET Core專案進行測試。 一、專案建立 首先,建

好代碼是管出來的——.Net Core中的單元測試與代碼覆蓋率

情況 其它 netcore output 窗口 一個數據庫 過濾 and 令行   測試對於軟件來說,是保證其質量的一個重要過程,而測試又分為很多種,單元測試、集成測試、系統測試、壓力測試等等,不同的測試的測試粒度和測試目標也不同,如單元測試關註每一行代碼,集成測試關註的是

Spark程式進行單元測試-使用scala

Spark 中進行一些單元測試技巧: 最近剛寫了一點Spark上的單元測試,大概整理了一些 rdd測試 spark程式一般從叢集中讀取資料然後通過rdd進行轉換,這其中涉及到叢集,每次修改bug,

轉 使用NUnit在.Net程式設計中進行單元測試

引言: 舉一個可能會發生在你身邊的事件將更能貼近實際,幸好我們現在就有一件在程式設計師看來非常普通的任務: 你今天第一天上班,你的專案經理拿給你一疊不算厚的文件,告訴你今天的任務是按照文件中的要求編寫一個.Net類,可能因為任務並不複雜,所以他看上去非常的隨意。 今天能否很好的完成任務對你來說非常特殊,你拿

淺談.Net Core後端單元測試

- [1. 前言](#head1) - [2. 為什麼需要單元測試](#head2) - [2.1 防止迴歸](#head3) - [2.2 減少程式碼耦合](#head4) - [3. 基本原則和規範](#head5) - [3.1 3A原則](#head6) - [3.2 儘量避免直接測試私有方法

使用xunit對asp.net core webapi進行集成測試

rtu sharp task 技術 分離 bubuko 們的 this eba 新項目我們采用前後端分離,後端采用asp.net core webapi, 如何對後端代碼進行自動化測試呢,有以下幾種方案: 1. 單元測試,目前這個方案對我們來說難度很大,拋開時間的問題,單

ASP.NET Core 對Controller進行單元測試

單元測試對我們的程式碼質量非常重要。很多同學都會對業務邏輯或者工具方法寫測試用例,但是往往忽略了對Controller層寫單元測試。我所在的公司沒見過一個對Controller寫過測試的。今天來演示下如果對Controller進行單元測試。以下內容預設您對單元測試有所瞭解,比如如何mock一個介面。在這裡多叨

使用Docker ComposeASP.NET Core程式新增MySQL資料庫

本文翻譯自: Adding MySQL to ASP.NET Core App With Docker Compose           之前的例子中我們將程式進行容器化,本文我們會把MySQL資料庫作為另一個Container,用於程式的訪問。因為現

使用Xunit進行單元測試

row 自動安裝 net 必須 清除 版本 多次 重構 nbsp 使用Xunit進行單元測試 來源 https://www.cnblogs.com/ccccc05/archive/2017/08/01/7266914.html 目前在.Net框架下的測試工具主要有

讓現有vue前端專案快速支援多語言 - 用.net core程式快速替換中文資源Key,咱不幹體力活

前言 這是我第一次發部落格,2020年立個flag以後要經常發。 最近應公司上層要求,需要將現有專案儘快支援多語言,而中文內容可以找專業人員翻譯。那麼咱們說幹就幹,首先我們專案的前端是用vue寫的spa程式且元件方面用的element ui,那麼自然而然想到用vue官方推薦的vue i18n,我快速過了下i1

NUnit.Framework在VS2015中如何進行單元測試

開放 ron 微軟 strong 擴展 分享 方案 mar 項目 微軟在VS2015中加入了自動化生成測試功能, 在需要測試的源文件的公共方法中右鍵既可以創建單元測試。 不過需要註意的是,要在公共方法中創建,否則會提示這個錯誤 如下是自動化單元測試界面,可以發

在vue-cli生成的項目中使用karma+chrome進行單元測試

使用 設計實現 測試用例 runner 服務 進行 ui界面 包含 node 用vue-cli生成項目時,如果選擇了單元測試,那麽會采用karma+mocha作為單元測試框架,默認使用的瀏覽器是PhantomJs。 Karma是一個基於Node.js的JavaScri

springMVC整合Junit4進行單元測試

main方法 pri tail println test pan ati 測試的 tco 版權聲明:本文為博主原創文章,未經博主允許不得轉載。 用Junit做單元測試的好處多多,博主領悟到了兩點。一是不用在每個類裏面都寫main方法然後去測試;二是可以得到每個方法執行

Dora.Interception,.NET Core度身打造的AOP框架:全新的版本

分享 ide 1.0 nuget hub tex 普通 inb .class Dora.Interception 1.0(Github地址:可以訪問GitHub地址:https://github.com/jiangjinnan/Dora)推出有一段時間了,最近花了點時間將它

Glib 對 C 函數進行單元測試

error ati 完成 structure 是否 pac str txt b- 1. Glib 單元測試框架 Glib 為單元測試提供了一套完整的測試框架,每個測試運行包括以下幾個部分 測試數據結構 測試 setup 與 teardown 函數 測試函數 2. 單元測

【轉載】IntelliJ IDEA配置JUnit進行單元測試

dbd ima pom.xml format height eight nsh 格式 oot 前提條件 安裝JDK,並配置好環境變量 工程已解決JUnit依賴關系(pom.xml) IDEA中JUnit配置 IDEA自帶一個JUnit插件,打開Settings窗口搜