前言
我們已經介紹了基本寫單元測試的方法,接下來我們就可以回頭拿先前製作的 API 當作練習,補上該有的測試。
本文
首先我們建立一個放置測試專案的資料夾。
接著建立完測試專案。
2021/02/21更新 原本是只想開一個測試專案把 Repository 跟 Service 的測試都放在一起後來想想不太對。 更新後的專案結構如下。
因為是要對 Service 層進行測試,記得要加入專案參考。
接下來先把基礎測試專案需要用到的東西準備好。
[TestClass]
public class AccountServiceTest
{
private IAccountRepository _accountRepository;
private IMapper _mapper
{
get
{
var config = new MapperConfiguration(options => { options.AddProfile<ServiceProfile>(); });
return config.CreateMapper();
}
}
[TestInitialize]
public void TestInitialize()
{
this._accountRepository = Substitute.For<IAccountRepository>();
}
private AccountService GetSystemUnderTest()
{
var sut = new AccountService(this._accountRepository,this._mapper);
return sut;
}
[TestMethod]
public void TestMethod1()
{
}
}
這邊需要特別注意的地方在 AutoMapper 的地方,我們並不需要製作一個假的 AutoMapper,所以可以直接使用原本方法。
接下來就可以開始寫單元測試了。
AddAccount(新增帳號)
1、有四個必填欄位需要確認。
[TestMethod]
[Owner("Sian")]
[TestCategory("AccountServiceTest")]
[TestProperty("AccountServiceTest", "AddAccount")]
public void AddAccount_Account為空_應回傳Exception()
{
//assert
var sut = this.GetSystemUnderTest();
var info = new AccountInfoModel
{
Account = string.Empty,
Email = "test@gmail.com",
Password = "123456",
Phone = "0917888444"
};
//act
Action actual = () => sut.AddAccount(info);
//arrange
actual.Should().Throw<Exception>()
.Which.Message.Contains("請檢查輸入欄位,缺一不可!");
}
[TestMethod]
[Owner("Sian")]
[TestCategory("AccountServiceTest")]
[TestProperty("AccountServiceTest", "AddAccount")]
public void AddAccount_Email為空_應回傳Exception()
{
//assert
var sut = this.GetSystemUnderTest();
var info = new AccountInfoModel
{
Account = "test",
Email = string.Empty,
Password = "123456",
Phone = "0917888444"
};
//act
Action actual = () => sut.AddAccount(info);
//arrange
actual.Should().Throw<Exception>()
.Which.Message.Contains("請檢查輸入欄位,缺一不可!");
}
[TestMethod]
[Owner("Sian")]
[TestCategory("AccountServiceTest")]
[TestProperty("AccountServiceTest", "AddAccount")]
public void AddAccount_Password為空_應回傳Exception()
{
//assert
var sut = this.GetSystemUnderTest();
var info = new AccountInfoModel
{
Account = "test",
Email = "test@gmail.com",
Password = string.Empty,
Phone = "0917888444"
};
//act
Action actual = () => sut.AddAccount(info);
//arrange
actual.Should().Throw<Exception>()
.Which.Message.Contains("請檢查輸入欄位,缺一不可!");
}
[TestMethod]
[Owner("Sian")]
[TestCategory("AccountServiceTest")]
[TestProperty("AccountServiceTest", "AddAccount")]
public void AddAccount_Phone為空_應回傳Exception()
{
//assert
var sut = this.GetSystemUnderTest();
var info = new AccountInfoModel
{
Account = "test",
Email = "test@gmail.com",
Password = "123456",
Phone = string.Empty
};
//act
Action actual = () => sut.AddAccount(info);
//arrange
actual.Should().Throw<Exception>()
.Which.Message.Contains("請檢查輸入欄位,缺一不可!");
}
結果:
2、使用者的帳號不可以重複。
[TestMethod]
[Owner("Sian")]
[TestCategory("AccountServiceTest")]
[TestProperty("AccountServiceTest", "AddAccount")]
public void AddAccount_使用者帳號重複_應回傳錯誤訊息()
{
//assert
var sut = this.GetSystemUnderTest();
var info = new AccountInfoModel
{
Account = "test",
Email = "test@gmail.com",
Password = "123456",
Phone = "0917888111"
};
var data = new AccountDataModel
{
Account = "test",
Email = "test@gmail.com",
Phone = "0917888111"
};
var expect = new ResultDto
{
Success = false,
Message = "該使用者帳號已存在,請確認!"
};
this._accountRepository.GetAccount(Arg.Any<string>()).Returns(data);
//act
var actual = sut.AddAccount(info);
//arrange
actual.Should().BeEquivalentTo(expect);
}
結果:
3、密碼長度不可低於6碼。
[TestMethod]
[Owner("Sian")]
[TestCategory("AccountServiceTest")]
[TestProperty("AccountServiceTest", "AddAccount")]
public void AddAccount_密碼長度低於6碼_應回傳錯誤訊息()
{
//assert
var sut = this.GetSystemUnderTest();
var info = new AccountInfoModel
{
Account = "test",
Email = "test@gmail.com",
Password = "1234",
Phone = "0917888111"
};
var expect = new ResultDto
{
Success = false,
Message = "使用者密碼長度不可低於6碼!"
};
//act
var actual = sut.AddAccount(info);
//arrange
actual.Should().BeEquivalentTo(expect);
}
結果:
4、密碼需透過加密處理存入資料庫。
這邊比較特別的是 ConverPassword 這個 Function 是 Private,一般來說我們並不會特別針對 Private Function 做測試,理由是一個 Private Function 一定會在某個 Public Function 被執行。
當然要對 Private Function 進行測試也不是不行的,可以使用以下方式。
[TestMethod]
[Owner("Sian")]
[TestCategory("AccountServiceTest")]
[TestProperty("AccountServiceTest", "ConverPassword")]
public void ConverPassword_輸入密碼_應回傳加密結果()
{
//assert
var sut = this.GetSystemUnderTest();
var method = sut.GetType().GetMethod("ConverPassword", BindingFlags.Instance | BindingFlags.NonPublic);
var expect = "9GYVaHLoOg+y+V/HHwKtkzBH3y8XWn14h8ifWPYViLc=";
//act
var actual = method.Invoke(sut, new[] { "test2", "12371324" });
//arrange
actual.Should().Be(expect);
}
結果:
這邊特別提醒一下,.NET Core 似乎已經沒有Private Object這個 Class 了,所以另尋出入找了這個方法。
021/02/21更新 發現公司大神提供了另一個方法,還不趕快更新一下。
因為 Private 只能透過上面那種方式測試,但如果是 Internal 那就有不同做法了。
首先我們試著把 ConverPassword 這個 function 改成 Internal。
接著需要安裝 Meziantou.MSBuild.InternalsVisibleTo 這個 Nuget 套件。
請注意這邊安裝的是目標專案,並不是測試專案喔!!!
此套件的使用方法可以參照這個地方。
安裝完後要到 Service 的 csproj 檔新增以下設定。
對專案左鍵點擊兩下即可進入 csproj。
<ItemGroup>
<InternalsVisibleTo Include="$(AssemblyName)Tests"/>
//專案名稱有Tests結尾的都可以看到Internal
</ItemGroup>
接下來到我們的測試專案原本測試 Private function 的地方。
修改一下寫法。
[TestMethod]
[Owner("Sian")]
[TestCategory("AccountServiceTest")]
[TestProperty("AccountServiceTest", "ConverPassword")]
public void ConverPassword_輸入密碼_應回傳加密結果()
{
//assert
var sut = this.GetSystemUnderTest();
var expect = "9GYVaHLoOg+y+V/HHwKtkzBH3y8XWn14h8ifWPYViLc=";
//act
var actual = sut.ConverPassword("test2", "12371324" );
//arrange
actual.Should().Be(expect);
}
結果:
檢查信箱是否合法。
同上一樣可以透過以下方式確認這個 Function 是否正確。
[TestMethod]
[Owner("Sian")]
[TestCategory("AccountServiceTest")]
[TestProperty("AccountServiceTest", "CheckMailFormate")]
public void CheckMailFormate_輸入錯誤格式Mail_應回傳False()
{
//assert
var sut = this.GetSystemUnderTest();
var method = sut.GetType().GetMethod("CheckMailFormate", BindingFlags.Instance | BindingFlags.NonPublic);
//act
var actual = method.Invoke(sut, new[] { "fff#gmail.com"});
//arrange
actual.Should().Be(false);
}
[TestMethod]
[Owner("Sian")]
[TestCategory("AccountServiceTest")]
[TestProperty("AccountServiceTest", "CheckMailFormate")]
public void CheckMailFormate_輸入正確格式Mail_應回傳True()
{
//assert
var sut = this.GetSystemUnderTest();
var method = sut.GetType().GetMethod("CheckMailFormate", BindingFlags.Instance | BindingFlags.NonPublic);
//act
var actual = method.Invoke(sut, new[] { "fff@gmail.com" });
//arrange
actual.Should().Be(true);
}
結果:
接下來別忘了要回去測試我們檢查信箱後回傳的訊息。
[TestMethod]
[Owner("Sian")]
[TestCategory("AccountServiceTest")]
[TestProperty("AccountServiceTest", "AddAccount")]
public void AddAccount_輸入信箱格式部正確_應回傳錯誤訊息()
{
//assert
var sut = this.GetSystemUnderTest();
var info = new AccountInfoModel
{
Account = "test",
Email = "test#gmail.com",
Password = "1234567",
Phone = "0917888111"
};
var expect = new ResultDto
{
Success = false,
Message = "請確認信箱格式!"
};
//act
var actual = sut.AddAccount(info);
//arrange
actual.Should().BeEquivalentTo(expect);
}
結果:
新增結果。
[TestMethod]
[Owner("Sian")]
[TestCategory("AccountServiceTest")]
[TestProperty("AccountServiceTest", "AddAccount")]
public void AddAccount_新增成功_應回傳正確訊息()
{
//assert
var sut = this.GetSystemUnderTest();
var info = new AccountInfoModel
{
Account = "test",
Email = "test@gmail.com",
Password = "1234567",
Phone = "0917888111"
};
this._accountRepository.AddAccount(Arg.Any<AccountCondition>()).Returns(true);
var expect = new ResultDto
{
Success = true,
Message = "新增成功"
};
//act
var actual = sut.AddAccount(info);
//arrange
actual.Should().BeEquivalentTo(expect);
}
[TestMethod]
[Owner("Sian")]
[TestCategory("AccountServiceTest")]
[TestProperty("AccountServiceTest", "AddAccount")]
public void AddAccount_新增失敗_應回傳錯誤訊息()
{
//assert
var sut = this.GetSystemUnderTest();
var info = new AccountInfoModel
{
Account = "test",
Email = "test@gmail.com",
Password = "1234567",
Phone = "0917888111"
};
this._accountRepository.AddAccount(Arg.Any<AccountCondition>()).Returns(false);
var expect = new ResultDto
{
Success = false,
Message = "新增失敗"
};
//act
var actual = sut.AddAccount(info);
//arrange
actual.Should().BeEquivalentTo(expect);
}
結果:
後記
這樣我們第一個功能新增帳號的 Service 部分就完成了,接下來後續示範就會加緊腳步,比較特殊地方才會拿出來講解。