Posted on Leave a comment

單元測試 – 重構測試

重構test的重要性

好的測試應該要容易維護,容易閱讀,
不應包含程式邏輯在內,因此像是if, while, for迴圈等都不應該出現在測試裡
如果我們驗證的內容會和資料有關,則建議使用Substitute,這樣可以讓我們能夠在每一個測試裡增加不同的資料
而且可以直接在每個測試裡看到資料的內容是什麼
下面是一個範例
private void AmountShouldBe(int expected, DateTime start, DateTime end)
{
IList data = new List()
{
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。

重構的測試的範例

下面為重構後的程式碼:
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();

_fakeToken = Substitute.For();

_logger = Substitute.For();

_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(“account: joey try to login failed”));
_logger.Received(1).Save(Arg.Is(m => m.Contains(“joey”) && m.Contains(“login failed”)));
}
}
}

Leave a Reply