本文
初始化與清除使用的 Attribute
上一篇我們介紹到使用改變時間的用法,但因為其中一個測試改變了時間可能會影響到其他單元測試,這時候就有幾個 Attribute 可以派上用場。
Attribute | 用法 | 頻率 |
---|---|---|
TestInitialize() | 在執行每一項測試之前,會先執行 TestInitialize 的程式碼。 | 每執行一個 TestMethod 會觸發一次。 |
TestCleanup() | 在執行每一項測試之後,會執行 TestCleanup 的程式碼。 | 每執行一個 TestMethod 會觸發一次。 |
ClassInitialize() | 在執行該類別的測試之前,會先執行 ClassInitialize 的程式碼。 | 以測試類別為單位執行一次。 |
ClassCleanup() | 在執行該類別的測試之後,會執行 ClassCleanup 的程式碼。 | 以測試類別為單位執行一次。 |
AssemblyInitialize() | 在執行所有類別的測試之前,會先執行 AssemblyInitialize 的程式碼。 | 以測試專案為單位執行一次。 |
AssemblyCleanup() | 在執行該類別的測試之後,會執行 AssemblyCleanup 的程式碼。 | 以測試專案為單位執行一次。 |
練習:
如題為了避免我們改變時間進而影響到其他單元測試,我們在執行每一個測試前後都會初始化一次時間。
[TestInitialize]
public void TestInitial()
{
SystemTime.SetToday = () => DateTime.Today;
}
[TestCleanup]
public void TestClean()
{
SystemTime.SetToday = () => DateTime.Today;
}
隔離物件
測試的時候我們會需要隔離要測試的物件,如果直接相依物件會產生一些不必要的問題導致測試結果可能有誤。
相依物件 | 可能發生情況 |
---|---|
資料庫 | 直接相依資料庫的情況下,如果有異動到資料,每一次都要還原資料。 |
外部服務 | 會因為網路連線狀態而直接影響測試執行的速度。 |
特殊商業邏輯 | 無法進行測試,例如:亂數資料、加密資料等。 |
所以為了解決這個問題,就可以很簡單的利用先前教學所使用的依賴注入方式,透過介面注入抽換實體,那麼我在在測試的地方,就能直接做一個測試用的實體出來進行模擬測試。
我們先建立一個示範用的 Common 專案裡面只放了一個共用的Model。
public class AccountModel
{
public string UserName { get; set; }
public string Account { get; set; }
}
接下來建立一個示範用的 Repository 專案,並建立一個假的取資料方法。
public interface IRepository
{
/// <summary>
/// 取得帳號
/// </summary>
/// <param name="account"></param>
/// <returns></returns>
AccountModel GetAccount(string account);
}
public class Repository : IRepository
{
public AccountModel GetAccount(string account)
{
//這邊應該是要跟DB拿取資料,現在先假裝一下請大家不要這麼認真。
var data = new List<AccountModel>
{
new AccountModel
{
Account = "123",
UserName = "Sian"
},
new AccountModel
{
Account = "456",
UserName = "Tom"
}
};
return data.Where(x => x.Account == account).FirstOrDefault();
}
}
接著建立 Service 的介面並實作,透過注入的方式跟 Repository 拿取資料。
public interface IService
{
/// <summary>
/// 取得帳號
/// </summary>
/// <param name="account"></param>
/// <returns></returns>
AccountModel GetAccount(string account);
}
public class Service : IService
{
private readonly IRepository _repository;
public Service(
IRepository repository)
{
this._repository = repository;
}
public AccountModel GetAccount(string account)
{
if (string.IsNullOrWhiteSpace(account))
{
throw new ArgumentNullException();
}
var data = this._repository.GetAccount(account);
return data;
}
}
接下來我們在測試的地方,就能使用繼承介面的方式抽換實體物件,製造一個假的物件供我們測試使用。
[TestClass()]
public class ServiceTests
{
[TestMethod()]
[Owner("Sian")]
[TestCategory("ServiceTest")]
[TestProperty("ServiceTests", "GetAccount")]
public void GetAccount_傳入存在的帳號_應回傳正確帳號資訊()
{
//arrange
var account = "123";
var expect = new AccountModel
{
Account = "123",
UserName = "test"
};
var service = new AccountService();
//act
var actual = service.GetAccount(account);
//assert
actual.Should().BeEquivalentTo(expect);
}
}
public class AccountService : IService
{
public AccountModel GetAccount(string account)
{
return new AccountModel
{
Account = "123",
UserName = "test"
};
}
}
結果
現在我們知道了如何隔離物件,但如果每一次都要我們手動寫一個假實體來進行驗證程式是否正確,那還沒寫完測試已經累死了,這時候就要介紹一個好用的工具了。
NSubstitute
首先我們先到 Nuget 安裝套件。
接著修改我們的單元測試使用 NSubstitute 建立假實體。
[TestMethod()]
[Owner("Sian")]
[TestCategory("ServiceTest")]
[TestProperty("ServiceTests", "GetAccount")]
public void GetAccount_傳入存在的帳號_應回傳正確帳號資訊()
{
//arrange
var account = "123";
var expect = new AccountModel
{
Account = "123",
UserName = "test"
};
var service = Substitute.For<IService>();
service.GetAccount(account).Returns(expect);
//act
var actual = service.GetAccount(account);
//assert
actual.Should().NotBeNull();
actual.Should().BeEquivalentTo(expect);
}
結果:
後續我們會在實作API中使用更多種方式,若對 NSubstitute 操作想先認識更多的可參考: