重構test的重要性
好的測試應該要容易維護,容易閱讀,
不應包含程式邏輯在內,因此像是if, while, for迴圈等都不應該出現在測試裡
如果我們驗證的內容會和資料有關,則建議使用Substitute,這樣可以讓我們能夠在每一個測試裡增加不同的資料
而且可以直接在每個測試裡看到資料的內容是什麼
下面是一個範例
1 2 3 4 5 6 7 8 9 10 11 12 13 |
private void AmountShouldBe(int expected, DateTime start, DateTime end) { IList<Budget> data = new List<Budget>() { new Budget() {Amount = 310, YearMonth = "201801"}, new Budget() {Amount = 620, YearMonth = "201803"}, new Budget() {Amount = 900, YearMonth = "201804"} }; var budgetCalculator = new BudgetCalculator(new TestDataBudgetRepository(data)); var budget = budgetCalculator.TotalAmount(start, end); Assert.AreEqual(expected, budget); } |
91的課程中建議每一次寫成功一個測試案例,就可以先做重構,這樣之後的的開發速度可以更加速
重構測試的步驟
- 抽出field: mock object(繼承假物件去創建假物件), stub(用Substitute)
- Setup: SUT初始化(或者[TestInitialize])
- 抽出方法(用Given為開頭):定義mock object行為,代表假如在跑這個scenario時…
- Extra Method with “Shouldxxx()” => SUT行為+Assertion
也就是盡可能讓我們的重構能夠符合3A原則,
3A pattern: Arrange, Act, Assert
上面重構後Arrange就用Given…,然後Act就是SUT行為,Assert就是Assertion。
重構的測試的範例
下面為重構後的程式碼:
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 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using NSubstitute; using NUnit.Framework; using RsaSecureToken; using Assert = NUnit.Framework.Assert; namespace RsaSecureToken.Tests { [TestFixture] public class AuthenticationServiceTests { private IProfile _fakeProfile; private IRsaToken _fakeToken; private ILogger _logger; private AuthenticationService _authenticationService; [SetUp] public void Given() { _fakeProfile = Substitute.For<IProfile>(); _fakeToken = Substitute.For<IRsaToken>(); _logger = Substitute.For<ILogger>(); _authenticationService = new AuthenticationService(_fakeProfile, _fakeToken, _logger); } private void GivenToken(string token) { _fakeToken.GetRandom("").ReturnsForAnyArgs(token); } private void GivenPassword(string account, string password) { _fakeProfile.GetPassword(account).Returns(password); } private void ShouldBeValid(string account, string password) { var actual = _authenticationService.IsValid(account, password, _logger); Assert.IsTrue(actual); } [Test()] public void IsValidTest() { GivenPassword(account: "joey", password: "91"); GivenToken(token: "000000"); ShouldBeValid(account: "joey", password: "91000000"); } [Test()] public void IsInValidTest() { GivenPassword(account: "joey", password: "91"); GivenToken(token: "000000"); ShouldBeInValid(account: "joey", errorPassword: "error password"); } private void ShouldBeInValid(string account, string errorPassword) { var actual = _authenticationService.IsValid(account, errorPassword, _logger); Assert.IsFalse(actual); } [Test()] public void ShouldLog() { GivenPassword(account: "joey", password: "91"); GivenToken(token: "000000"); ShouldBeInValid(account: "joey", errorPassword: "error password"); //這個可能會有過度指定的問題,或許加一個痘號就會導致測試失敗 //_logger.Received(1).Save(Arg.Is<string>("account: joey try to login failed")); _logger.Received(1).Save(Arg.Is<string>(m => m.Contains("joey") && m.Contains("login failed"))); } } } |