1. 程式人生 > >使用xUnit為.net core程式進行單元測試 -- Assert

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

Assert

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

xUnit提供了以下型別的Assert:

  • boolean:True/False
  • String:相等/不等,是否為空,以..開始/結束,是否包含子字串,匹配正則表示式
  • 數值型:相等/不等,是否在某個範圍內,浮點的精度
  • Collection:內容是否相等,是否包含某個元素,是否包含滿足某種條件(predicate)的元素,是否所有的元素都滿足某個assert
  • Raised events:Custom events,Framework events(例如:PropertyChanged)
  • Object Type:是否是某種型別,是否某種型別或繼承與某種型別

一個test裡應該有多少個asserts?

一種建議的做法是,每個test方法裡面只有一個assert。

而還有一種建議就是,每個test裡面可以有多個asserts,只要這些asserts都是針對同一個行為就行。

第一個Assert

目標類:

複製程式碼
    public class Patient
    {
        public Patient()
        {
            IsNew = true;
        }

        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string FullName => $"{FirstName} {LastName}";
        public int HeartBeatRate { get; set; }
        public bool IsNew { get; set; }

        public void IncreaseHeartBeatRate()
        {
            HeartBeatRate = CalculateHeartBeatRate() + 2;
        }

        private int CalculateHeartBeatRate()
        {
            var random = new Random();
            return random.Next(1, 100);
        }
    }
複製程式碼

測試類:

複製程式碼
    public class PatientShould
    {
        [Fact]
        public void HaveHeartBeatWhenNew()
        {
            var patient = new Patient();

            Assert.True(patient.IsNew);
        }
    }
複製程式碼

執行測試:

 

結果符合預期,測試通過。

改為Assert.False()的話:

測試Fail。

String Assert

測試string是否相等

        [Fact]
        public void CalculateFullName()
        {
            var p = new Patient
            {
                FirstName = "Nick",
                LastName = "Carter"
            };
            Assert.Equal("Nick Carter", p.FullName);
        }

然後你需要Build一下,這樣VS Test Explorer才能發現新的test。

執行測試,結果Pass:

同樣改一下Patient類(別忘了Build一下),讓結果失敗:

從失敗資訊可以看到期待值和實際值。

StartsWith, EndsWith

        [Fact]
        public void CalculateFullNameStartsWithFirstName()
        {
            var p = new Patient
            {
                FirstName = "Nick",
                LastName = "Carter"
            };
            Assert.StartsWith("Nick", p.FullName);
        }

        [Fact]
        public void CalculateFullNameEndsWithFirstName()
        {
            var p = new Patient
            {
                FirstName = "Nick",
                LastName = "Carter"
            };
            Assert.EndsWith("Carter", p.FullName);e);
        }

Build,然後Run Test,結果Pass:

忽略大小寫 ignoreCase

string預設的Assert是區分大小寫的,這樣就會失敗:

可以為這些方法新增一個引數ignoreCase設定為true,就會忽略大小寫:

包含子字串 Contains

        [Fact]
        public void CalculateFullNameSubstring()
        {
            var p = new Patient
            {
                FirstName = "Nick",
                LastName = "Carter"
            };
            Assert.Contains("ck Ca", p.FullName);
        }

Build,測試結果Pass。

正則表示式,Matches

測試一下First name和Last name的首字母是不是大寫的:

        [Fact]
        public void CalculcateFullNameWithTitleCase()
        {
            var p = new Patient
            {
                FirstName = "Nick",
                LastName = "Carter"
            };
            Assert.Matches("[A-Z]{1}{a-z}+ [A-Z]{1}[a-z]+", p.FullName);
        }

Build,測試通過。

數值 Assert

首先為Patient類新增一個property: BloodSugar。

    public class Patient
    {
        public Patient()
        {
            IsNew = true;
            _bloodSugar = 5.0f;
        }

        private float _bloodSugar;
        public float BloodSugar
        {
            get { return _bloodSugar; }
            set { _bloodSugar = value; }
        }
        ...

Equal:

        [Fact]
        public void BloodSugarStartWithDefaultValue()
        {
            var p = new Patient();
            Assert.Equal(5.0, p.BloodSugar);
        }

Build,測試通過。

範圍, InRange:

首先為Patient類新增一個方法,病人吃飯之後血糖升高:

        public void HaveDinner()
        {
            var random = new Random();
            _bloodSugar += (float)random.Next(1, 1000) / 100; //  應該是1000
        }

新增test:

        [Fact]
        public void BloodSugarIncreaseAfterDinner()
        {
            var p = new Patient();
            p.HaveDinner();
            // Assert.InRange<float>(p.BloodSugar, 5, 6);
            Assert.InRange(p.BloodSugar, 5, 6);
        }

Build,Run Test,結果Fail:

可以看到期待的Range和實際的值,這樣很好。如果你使用Assert.True(xx >= 5 && xx <= 6)的話,錯誤資訊只能顯示True或者False。

因為HaveDinner方法裡,表示式的分母應該是1000,修改後,Build,Run,測試Pass。

浮點型數值的Assert

在被測專案新增這兩個類:

namespace Hospital
{
    public abstract class Worker
    {
        public string Name { get; set; }

        public abstract double TotalReward { get; }
        public abstract double Hours { get; }
        public double Salary => TotalReward / Hours;
    }

    public class Plumber : Worker
    {
        public override double TotalReward => 200;
        public override double Hours => 3;
    }
}

然後針對Plumber建立一個測試類 PlumberShould.cs, 並建立第一個test:

namespace Hospital.Tests
{
    public class PlumberShould
    {
        [Fact]
        public void HaveCorrectSalary()
        {
            var plumber = new Plumber();
            Assert.Equal(66.666, plumber.Salary);
        }
    }
}

Build專案, 然後再Test Explorer裡面選擇按Class分類顯示Tests:

Run Selected Test, 結果會失敗:

這是一個精度的問題.

在Assert.Equal方法, 可以新增一個precision引數, 設定精度為3:

        [Fact]
        public void HaveCorrectSalary()
        {
            var plumber = new Plumber();
            Assert.Equal(66.666, plumber.Salary, precision: 3);
        }

Build, Run Test:

因為有四捨五入的問題, 所以test仍然fail了.

所以還需要改一下:

        [Fact]
        public void HaveCorrectSalary()
        {
            var plumber = new Plumber();
            Assert.Equal(66.667, plumber.Salary, precision: 3);
        }

這次會pass的:

Assert Null值

        [Fact]
        public void NotHaveNameByDefault()
        {
            var plumber = new Plumber();
            Assert.Null(plumber.Name);
        }

        [Fact]
        public void HaveNameValue()
        {
            var plumber = new Plumber
            {
                Name = "Brian"
            };
            Assert.NotNull(plumber.Name);
        }

有兩個方法, Assert.Null 和 Assert.NotNull, 直接傳入期待即可.

測試會Pass的.

集合 Collection Assert

修改一下被測試類, 新增一個集合屬性, 並賦值:

namespace Hospital
{
    public abstract class Worker
    {
        public string Name { get; set; }

        public abstract double TotalReward { get; }
        public abstract double Hours { get; }
        public double Salary => TotalReward / Hours;

        public List<string> Tools { get; set; }
    }

    public class Plumber : Worker
    {
        public Plumber()
        {
            Tools = new List<string>()
            {
                "螺絲刀",
                "扳子",
                "鉗子"
            };
        }

        public override double TotalReward => 200;
        public override double Hours => 3;
    }
}

測試是否包含某個元素, Assert.Contains():

        [Fact]
        public void HaveScrewdriver()
        {
            var plumber = new Plumber();
            Assert.Contains("螺絲刀", plumber.Tools);
        }

Build, Run Test, 結果Pass.

修改一下名字, 讓其Fail:

這個失敗資訊還是很詳細的.

相應的還有一個Assert.DoesNotContain()方法, 測試集合是否不包含某個元素.

        [Fact]
        public void NotHaveKeyboard()
        {
            var plumber = new Plumber();
            Assert.DoesNotContain("鍵盤", plumber.Tools);
        }

這個test也會pass.

Predicate:

測試一下集合中是否包含符合某個條件的元素:

        [Fact]
        public void HaveAtLeastOneScrewdriver()
        {
            var plumber = new Plumber();
            Assert.Contains(plumber.Tools, t => t.Contains("螺絲刀"));
        }

使用的是Assert.Contains的一個overload方法, 它的第一個引數是集合, 第二個引數是Predicate.

Build, Run Test, 會Pass的.

比較集合相等:

新增Test:

        [Fact]
        public void HaveAllTools()
        {
            var plumber = new Plumber();
            var expectedTools = new []
            {
                "螺絲刀",
                "扳子",
                "鉗子"
            };
            Assert.Equal(expectedTools, plumber.Tools);
        }

注意, Plumber的tools型別是List, 這裡的expectedTools型別是array.

這個test 仍然會Pass.

如果修改一個元素, 那麼測試會Fail, 資訊如下:

Assert針對集合的每個元素:

如果想對集合的每個元素進行Assert, 當然可以通過迴圈來Assert了, 但是更好的寫法是呼叫Assert.All()方法:

        [Fact]
        public void HaveNoEmptyDefaultTools()
        {
            var plumber = new Plumber();
            Assert.All(plumber.Tools, t => Assert.False(string.IsNullOrEmpty(t)));
        }

這個測試會Pass.

如果在被測試類的Tools屬性新增一個空字串, 那麼失敗資訊會是:

這裡寫到, 4個元素裡面有1個沒有pass.

針對Object型別的Assert

 首先再新增一個Programmer類:

    public class Programmer : Worker
    {
        public override double TotalReward => 1000;
        public override double Hours => 3.5;
    }

然後建立一個WorkerFactory:

namespace Hospital
{
    public class WorkerFactory
    {
        public Worker Create(string name, bool isProgrammer = false)
        {
            if (isProgrammer)
            {
                return new Programmer { Name = name };
            }
            return new Plumber { Name = name };
        }
    }
}

判斷是否是某個型別 Assert.IsType<Type>(xx):
建立一個測試類 WorkerShould.cs和一個test:

namespace Hospital.Tests
{
    public class WorkerShould
    {
        [Fact]
        public void CreatePlumberByDefault()
        {
            var factory = new WorkerFactory();
            Worker worker = factory.Create("Nick");
            Assert.IsType<Plumber>(worker);
        }
    }
}

Build, Run Test: 結果Pass.

相應的, 還有一個Assert.IsNotType<Type>(xx)方法.

利用Assert.IsType<Type>(xx)的返回值, 它會返回Type(xx的)的這個例項, 添加個一test:

        [Fact]
        public void CreateProgrammerAndCastReturnedType()
        {
            var factory = new WorkerFactory();
            Worker worker = factory.Create("Nick", isProgrammer: true);
            Programmer programmer = Assert.IsType<Programmer>(worker);
            Assert.Equal("Nick", programmer.Name);
        }

Build, Run Tests: 結果Pass.

Assert針對父類:

寫這樣一個test, 建立的是一個promgrammer, Assert的型別是它的父類Worker:

        [Fact]
        public void CreateProgrammer_AssertAssignableTypes()
        {
            var factory = new WorkerFactory();
            Worker worker = factory.Create("Nick", isProgrammer: true);
            Assert.IsType<Worker>(worker);
        }

這個會Fail:

這時就應該使用這個方法, Assert.IsAssignableFrom<祖先類>(xx):

        [Fact]
        public void CreateProgrammer_AssertAssignableTypes()
        {
            var factory = new WorkerFactory();
            Worker worker = factory.Create("Nick", isProgrammer: true);
            Assert.IsAssignableFrom<Worker>(worker);
        }

Build, Run Tests: Pass.

Assert針對物件的例項

判斷兩個引用是否指向不同的例項 Assert.NotSame(a, b):

        [Fact]
        public void CreateSeperateInstances()
        {
            var factory = new WorkerFactory();
            var p1 = factory.Create("Nick");
            var p2 = factory.Create("Nick");
            Assert.NotSame(p1, p2);
        }

由工廠建立的兩個物件是不同的例項, 所以這個test會Pass.

相應的還有個Assert.Same(a, b) 方法.

Assert 異常

為WorkFactory先新增一個異常處理:

namespace Hospital
{
    public class WorkerFactory
    {
        public Worker Create(string name, bool isProgrammer = false)
        {
            if (name == null)
            {
                throw new ArgumentNullException(nameof(name));
            }
            if (isProgrammer)
            {
                return new Programmer { Name = name };
            }
            return new Plumber { Name = name };
        }
    }
}

如果在test執行程式碼時丟擲異常的話, 那麼test會直接fail掉.

所以應該使用Assert.Throws<ArgumentNullException>(...)方法來Assert是否丟擲了特定型別的異常.

新增一個test:

        [Fact]
        public void NotAllowNullName()
        {
            var factory = new WorkerFactory();
// var p = factory.Create(null); // 這個會失敗 Assert.Throws
<ArgumentNullException>(() => factory.Create(null)); }

注意不要直接執行會丟擲異常的程式碼. 應該在Assert.Throws<ET>()的方法裡新增lambda表示式來呼叫方法.

這樣的話就會pass.

如果被測試程式碼沒有丟擲異常的話, 那麼test會fail的. 把拋異常程式碼註釋掉之後再Run:

更具體的, 還可以指定引數的名稱:

        [Fact]
        public void NotAllowNullName()
        {
            var factory = new WorkerFactory();
            // Assert.Throws<ArgumentNullException>(() => factory.Create(null));
            Assert.Throws<ArgumentNullException>("name", () => factory.Create(null));
        }

這裡就是說異常裡應該有一個叫name的引數.

Run: Pass.

如果把"name"改成"isProgrammer", 那麼這個test會fail:

利用Assert.Throws<ET>()的返回結果, 其返回結果就是這個丟擲的異常例項.

        [Fact]
        public void NotAllowNullNameAndUseReturnedException()
        {
            var factory = new WorkerFactory();
            ArgumentNullException ex = Assert.Throws<ArgumentNullException>(() => factory.Create(null));
            Assert.Equal("name", ex.ParamName);
        }

Assert Events 是否發生(Raised)

回到之前的Patient類, 新增如下程式碼:

        public void Sleep()
        {
            OnPatientSlept();
        }

        public event EventHandler<EventArgs> PatientSlept;

        protected virtual void OnPatientSlept()
        {
            PatientSlept?.Invoke(this, EventArgs.Empty);
        }

然後回到PatientShould.cs新增test:

        [Fact]
        public void RaiseSleptEvent()
        {
            var p = new Patient();
            Assert.Raises<EventArgs>(
                handler => p.PatientSlept += handler, 
                handler => p.PatientSlept -= handler, 
                () => p.Sleep());
        }

Assert.Raises<T>()第一個引數是附加handler的Action, 第二個引數是分離handler的Action, 第三個Action是觸發event的程式碼.

Build, Run Test: Pass.

如果註釋掉Patient類裡Sleep()方法內部那行程式碼, 那麼test會fail:

針對INotifyPropertyChanged的特殊Assert:

修改Patient程式碼:

namespace Hospital
{
    public class Patient: INotifyPropertyChanged
    {
        public Patient()
        {
            IsNew = true;
            _bloodSugar = 5.0f;
        }

        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string FullName => $"{FirstName} {LastName}";
        public int HeartBeatRate { get; set; }
        public bool IsNew { get; set; }

        private float _bloodSugar;
        public float BloodSugar
        {
            get => _bloodSugar;
            set => _bloodSugar = value;
        }

        public void HaveDinner()
        {
            var random = new Random();
            _bloodSugar += (float)random.Next(1, 1000) / 1000;
            OnPropertyChanged(nameof(BloodSugar));
        }

        public void IncreaseHeartBeatRate()
        {
            HeartBeatRate = CalculateHeartBeatRate() + 2;
        }

        private int CalculateHeartBeatRate()
        {
            var random = new Random();
            return random.Next(1, 100);
        }

        public void Sleep()
        {
            OnPatientSlept();
        }

        public event EventHandler<EventArgs> PatientSlept;

        protected virtual void OnPatientSlept()
        {
            PatientSlept?.Invoke(this, EventArgs.Empty);
        }

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
View Code

新增一個Test:

        [Fact]
        public void RaisePropertyChangedEvent()
        {
            var p = new Patient();
            Assert.PropertyChanged(p, "BloodSugar", () => p.HaveDinner());
        }

針對INotifyPropertyChanged, 可以使用Assert.PropertyChanged(..) 這個專用的方法來斷定PropertyChanged的Event是否被觸發了.

Build, Run Tests: Pass.

到目前為止, 介紹的都是入門級的內容.

接下來要介紹的是稍微進階一點的內容了.

相關推薦

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

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

使用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程式進行單元測試(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 3.0 單元測試與 Asp.Net Core 3.0 整合測試

單元測試與整合測試 測試必要性說明 相信大家在看到單元測試與整合測試這個標題時,會有很多感慨,我們無數次的在實踐中提到要做單元測試、整合測試,但是大多數專案都沒有做或者僅建了專案檔案。這裡有客觀原因,已經接近交付日期了,我們沒時間做白盒測試了。也有主觀原因,面對業務複雜的程式碼我們不知道如何入手做單元測試,不

淺談.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. 單元測