首頁 C# Unit Testing Use NUnit
文章
Cancel

C# Unit Testing Use NUnit

快速建立專案的示範影片

選擇測試專案

選擇1.建立NUnit測試專案

直接建立NUnit 測試專案

Desktop View

選擇2.建立MSTest測試專案

直接建立MSTest測試專案,並安裝NUnit相關套件

安裝NUnit 與 NUnit3TestAdapter

Desktop View
NUnit:

1
NuGet\Install-Package NUnit -Version 3.13.3

NUnit3TestAdapter:

1
NuGet\Install-Package NUnit3TestAdapter -Version 4.3.0

NUnit.第一個測試

Hello World

如下方範例,主要使用的是Assert.That

UnitTesting.Test Source Code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
namespace UnitTesting.Test
{
    public class Tests
    {
        [SetUp]
        public void Setup()
        {
        }

        [Test]
        public void Test1()
        {
            var result = true;
            //下面三種寫法,選擇一種
            Assert.IsTrue(result);
            Assert.That(result, Is.True);
            Assert.That(result==true);
        }
    }
}

SetUp的使用方式

SetUp用法,用來減少Method中,每次都需要重新寫new Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
namespace UnitTesting.Test
{
    public class Tests
    {
        private Main main;
        [SetUp]
        public void Setup()
        {
            main=new Main();
        }

        [Test]
        public void Add()
        {
            var result= main.Add(1, 2);
            Assert.That(result,Is.EqualTo(3));
        }
        [Test]
        public void Add2()
        {
            var result = main.Add(1, 2);
            Assert.That(result, Is.EqualTo(3));
        }
    }
}

參數與TestCase的使用方式

如果相同的Method需要根據參數做不同的測試

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
namespace UnitTesting.Test
{
    public class Tests
    {
        private Main main;
        [SetUp]
        public void Setup()
        {
            main=new Main();
        }

        [Test]
        [TestCase(1,2,3)]
        [TestCase(1,2,3)]
        public void Add(int a,int b,int sum)
        {
            var result= main.Add(a, b);
            Assert.That(result,Is.EqualTo(sum));
        }

    }
}

重構單元測試的方式

Desktop View

參考語法

by Pass的方式

使用Ignore將不會執行當前Method的測試內容 (by Pass)

1
2
3
4
5
6
7
[TestCase(1,2,3)]
[Ignore("by Pass")]
public void Add(int a,int b,int sum)
{
var result= main.Add(a, b);
Assert.That(result,Is.EqualTo(sum));
}

檢查字串開頭的方式

字串開頭是否相等

1
2
3
4
5
6
[Test]
public void IsStartWith()
{
var result= "HellowWold";
Assert.That(result,Does.StartWith("Hell"));
}

檢查字串結尾的方式

字串結尾是否相等

1
2
3
4
5
6
[Test]
public void IsStartWith()
{
var result= "HellowWold";
Assert.That(result,Does.EndWith("Wold"));
}

檢查字串是否包含特定字串的方式

字串結尾是否包含

1
2
3
4
5
6
[Test]
public void IsStartWith()
{
var result= "HellowWold";
Assert.That(result,Does.Contain("wW"));
}

檢查陣列資料的方式

方法2

確認陣列內容是否一樣

1
2
3
4
5
6
[Test]
public void check_List_Context()
{
    var result = new List<int>() {1,2,3 };//這裡代換成輸入的資料
    Assert.That(result, Is.EquivalentTo(new[] { 1,2,3}));//這裡代換成要比對的正確資料
}

檢查回拋Error的方式

Error判斷 如果有個物件長這樣

1
2
3
4
5
6
7
8
9
10
11
12
13
public  class Main
{
   public string LastError { get; set; }
    public event EventHandler<Guid> Err;
    public void Log(string err)
    {
        if (string.IsNullOrWhiteSpace(err))
        {
            throw new ArgumentNullException();
        }
        LastError = err;
    }
}

測試要這樣寫

1
2
3
4
5
6
7
8
9
[Test]
[TestCase(null)]
[TestCase("")]
[TestCase(" ")]
public void check_List_Context(string err)
{
    var log=new Main();
    Assert.That(() => log.Log(err), Throws.ArgumentNullException);
}

透過重構建立更適合進行測試的程式

假設有個程式如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class service
{
  public string readTitle()
  {
      var str = File.ReadAllText("");
      return str;
  }
}
public class Video 
{
  public int id { get; set; }
  public string title { get; set; }
  public string IsProcessed { get; set; }
}

Step2.將「File.ReadAllText(“”)」這個功能,提取到到新的Class中,爾後調用這個Class進行使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class service
{
    public string readTitle()
    {
        var str = new FileReader().Read("");
        return str;
    }
}
public class Video 
{
    public int id { get; set; }
    public string title { get; set; }
    public string IsProcessed { get; set; }
}
public class FileReader
{
    public string Read(string Path)
    {
        return File.ReadAllText(Path);
    }
}

Step2.從新建的Class中,將Method提煉到到Interface中,爾後

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class service
{
    public string readTitle()
    {
        var str = new FileReader().Read("");
        return str;
    }
}
public class Video 
{
    public int id { get; set; }
    public string title { get; set; }
    public string IsProcessed { get; set; }
}
public interface IFileReader
{
    string Read(string str);
}
public class FileReader: IFileReader
{
    public string Read(string Path)
    {
        return File.ReadAllText(Path);
    }
}

重構過程如圖所示
Desktop View

使用FluentAssertions陣列資料

安裝FluentAssertions Desktop View

1
NuGet\Install-Package FluentAssertions -Version 6.8.0

使用方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Punlic class Member
{
    public int Id { get; set; }

    public string Name { get; set; }
}

[Test]
public void Test()
{
    var m1 = new Member() { Id = 1, Name = "111",Code="A" };
    var m2 = new Member() { Id = 3, Name = "211",Code="B" };

    m1.ShouldBeEquivalentTo(m2);
}

備註:ShouldBeEquivalentTo與Assert 同為測試驗證的語法

如果只想驗證Name欄位,不想驗證ID跟CODE欄位則追加 Excluding進行驗證

1
2
m1.ShouldBeEquivalentTo(m2,c=>c.Excluding(su=>su.Id)
. Excluding(su=>su.Code));

備註2:參考至

https://dotblogs.com.tw/yc421206/2015/06/20/151606

新增用來測試的假物件的方式

在單元測試裡面,新增用來測試的假物件

1
2
3
4
5
6
7
8
9
10
namespace UnitTesting.Test
{
    internal class FakeReader : IFileReader
    {
        public string Read(string str)
        {
            return "";
        }
    }
}

三種使用假物件進行測試的方式 1. Method Argument is interface

1
2
3
4
5
public string readTitle(IFileReader fileReader)
{
    var str = fileReader.Read("");
    return str;
}

then Testing Code

1
2
3
4
5
6
7
[Test]
public void test2()
{
    var service =new service();
    var result = service.readTitle(new FakeReader());
    Assert.That(result, Does.Contain("ERR"));
}

2. initial

1
2
3
4
5
6
7
8
9
10
11
12
13
public class service
{
    public IFileReader FileRead{get;set;}
    public service()
    {
      FileRead =new FileReader();
    }
    public string readTitle()
    {
        var str = FileRead.Read("");
        return str;
    }
}

then Testing Code

1
2
3
4
5
6
7
8
[Test]
public void test2()
{
    var service =new service();
    service.FileRead = new FileReader();
    var result = service.readTitle();
    Assert.That(result, Does.Contain("ERR"));
}

3. initial

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class service
{
    private IFileReader _FileRead;

    public service(IFileReader fileReader =null)
    {
      _FileRead =fileReader ?? new FileReader();
    }
    public string readTitle()
    {
        var str = _FileRead.Read("");
        //var Conver = JsonConvert.DeserializeObject<Video>(str);
        //if (Conver == null) return "Err";
        //return Conver.title;
        return str;
    }
}

then Testing Code

1
2
3
4
5
6
7
[Test]
public void test2()
{
    var service = new service(new FakeReader());
    var result = service.readTitle();
    Assert.That(result, Does.Contain("ERR"));
}

簡單使用Moq建立假物件

Nuget如下

1
NuGet\Install-Package Moq -Version 4.18.2

加入Moq後的重構方式 Desktop View

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using Moq;

namespace UnitTesting.Test
{
    public class Tests
    {
        private Main main;
        private Mock<IFileReader> _fileReader;
        private service _service;

        [SetUp]
        public void Setup()
        {
            main=new Main();
             _fileReader = new Mock<IFileReader>();
             _service = new service(_fileReader.Object);
        }
        [Test]
        public void test2()
        {    
            _fileReader.Setup(c=>c.Read(@"C:\temp\MyTxt.txt")).Returns("ERR");
            var result = _service.readTitle();
            Assert.That(result, Does.Contain("ERR"));
        }
    }
}

UnitOfWork 與 單元測試

Desktop View

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace UnitTesting
{
    public class BookHelp 
    {
        public static string BookingIsExust(Booking booking, IBookingRepository bookingRepository)
        {
            if (booking.Status == "Cancelled") return String.Empty;
            var tempBook = bookingRepository.GetBookings(booking.Id);
            var overlappingBooking = tempBook.FirstOrDefault();
            return overlappingBooking == null ? string.Empty : overlappingBooking.Ref;
        }
    }
    internal class UnitOfWork
    {
        public IQueryable<Booking> Query<T>()
        { 
            return new List<Booking>().AsQueryable(); 
        }
    }
    public interface IBookingRepository
    {
        IQueryable<Booking> GetBookings(int? id = null);
    }
    public class BookingRepository : IBookingRepository
    {
        public IQueryable<Booking> GetBookings(int? id=null)
        {
            var unitOfwork =new UnitOfWork();
            var bookings = unitOfwork.Query<Booking>().Where(c => c.Status != "Cancelled");
            if(id.HasValue)bookings=bookings.Where(c => c.Id != id.Value);  
            return bookings;
        }
    }
    public class Booking 
    {
        public string Status { get; set; }
        public int Id { get; set; }
        public DateTime ArrivalData { get; set; }
        public DateTime DepartureData { get; set; }
        public string Ref { get; set; }
    }
}

建立單元測試

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[Test]
public void test3()
{ 
    var mock =new Mock<IBookingRepository>();
    mock.Setup(c => c.GetBookings(1)).Returns(new List<Booking>
    {
        new Booking { Id = 2,
            ArrivalData = new DateTime(2022, 01, 01),
            DepartureData = new DateTime(2022, 03, 03),
            Ref="bbb"
        }
    }.AsQueryable());
    var result=BookHelp.BookingIsExust(new Booking
    {
        Id = 2,
        ArrivalData = new DateTime(2022, 01, 01),
        DepartureData = new DateTime(2022, 03, 03),
        Ref = "bbb"
    }, mock.Object);
    Assert.That(result,Is.Empty);
}

可以搭配的技術

本文由作者按照 CC BY 4.0 進行授權