STUB的功能
這邊是NSubstitute的說明:http://nsubstitute.github.io/help/getting-started/
Substitute是.NET裡的一個隔離框架,若要使用,需要額外在測試專案用NUGET去安裝NSubstitute
1. 動態產生假物件
2. 模擬回傳值
3. 測試監聽事件
4. 驗證傳入參數是否正確
使用Subsitute(Sub)
使用方法如下
1 |
calculator = Substitute.For<ICalculator>(); |
設定呼叫某個方法應該回傳的值
1 2 |
calculator.Add(1, 2).Returns(3); Assert.That(calculator.Add(1, 2), Is.EqualTo(3)); |
下面可以驗證是否Add這個FUNC有被呼叫到
1 2 3 |
calculator.Add(1, 2); calculator.Received().Add(1, 2); calculator.DidNotReceive().Add(5, 7); |
下面的程式能夠判別傳入的參數是不是正確
1 2 3 |
calculator.Add(10, -5); calculator.Received().Add(10, Arg.Any<int>()); calculator.Received().Add(10, Arg.Is<int>(x => x < 0)); |
驗證回傳值是否正確
1 2 3 4 |
calculator .Add(Arg.Any<int>(), Arg.Any<int>()) .Returns(x => (int)x[0] + (int)x[1]); Assert.That(calculator.Add(5, 10), Is.EqualTo(15)); |
使用Substitute來針對不同狀況實作假介面
這是一個用MOCK方法的範例
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.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; //using Microsoft.VisualStudio.TestTools.UnitTesting; using NUnit.Framework; using RsaSecureToken; using Assert = NUnit.Framework.Assert; namespace RsaSecureToken.Tests { [TestFixture] public class AuthenticationServiceTests { [Test()] public void IsValidTest() { var target = new AuthenticationService(new FakeProfile(), new FakeToken()); var actual = target.IsValid("joey", "91000000"); //always failed Assert.IsTrue(actual); } } public class FakeProfile:IProfile { public string GetPassword(string account) { if (account == "joey") { return "91"; } throw new Exception(); } } public class FakeToken:IRsaToken { public string GetRandom(string account) { return "000000"; } } } |
上面做法有什麼問題?
- 每一個不同的依賴案例就要製作一個不同的FakeObject,會讓寫測試的時間太久
- 沒辦法直接從程式碼知道為什麼這樣會是Vaild
動態產生物件的使用完整範例
1 |
Subsitute.For<T>() |
定義假物件行為(stub)
1 |
fake.方法(參數).Returns(值) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
[Test()] public void IsValidTest() { var fakeProfile = Substitute.For<IProfile>(); fakeProfile.GetPassword("joey").Returns("91"); var fakeToken = Substitute.For<IRsaToken>(); fakeToken.GetRandom("").ReturnsForAnyArgs("000000"); var target = new AuthenticationService(fakeProfile, fakeToken); var actual = target.IsValid("joey", "91000000"); //always failed Assert.IsTrue(actual); } } |
驗證某個函數是否被呼叫
使用mock object assertion
需求:驗證是非法的時候要記一個log
1 |
fake.Receive(次數).方法(參數驗證) |
不要用太多mock就算要使用要避免過度指定,也就是當prod code小小變動就導致測試程式壞掉
下面的程式碼可以驗證當呼叫SyncBookOrders()時是不是會呼叫Insert這個函數兩次:
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 |
[Test] public void Test_SyncBookOrders_3_Orders_Only_2_book_order() { var result = new List<Order> { new Order { Type = "Book" }, new Order { Type = "Book" }, new Order { Type = "Item" } }; var target = new OrderServiceForTest(); target.SetOrder(result); var fakeBookDao = Substitute.For<IBookDao>(); target.SetDao(fakeBookDao); target.SyncBookOrders(); fakeBookDao.Received(2).Insert(Arg.Is<Order>(m => m.Type == "Book")); } |
驗證傳入參數
驗證傳入的參數是否包含某些關鍵字
1 |
_logger.Received(1).Save(Arg.Is<string>(m => m.Contains("joey") && m.Contains("login failed"))); |