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

第一部分: http://www.cnblogs.com/cgzl/p/8283610.htmlhtml

Assert

Assert作什麼?Assert基於代碼的返回值、對象的最終狀態、事件是否發生等狀況來評估測試的結果。Assert的結果多是Pass或者Fail。若是全部的asserts都pass了,那麼整個測試就pass了;若是有任何assert fail了,那麼測試就fail了。正則表達式

xUnit提供瞭如下類型的Assert:dom

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

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

一種建議的作法是,每一個test方法裏面只有一個assert。ide

而還有一種建議就是,每一個test裏面能夠有多個asserts,只要這些asserts都是針對同一個行爲就行。測試

第一個Assert

目標類:ui

複製代碼
    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);
        }
    }
複製代碼

測試類:this

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

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

運行測試:spa

 

結果符合預期,測試經過。3d

改成Assert.False()的話:code

測試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.

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

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

相關文章
相關標籤/搜索