前言

本篇會介紹測試 Service 層好用工具,可以加快撰寫測試。

本文

CsvHelper

先前我們在測試方法時,遇到要取用資料都只能自己手刻。

這時候就可以使用一個好用的工具 CsvHelper。
這邊主要是將資料庫的 DATA 匯出 CSV 檔,透過 CsvHelper 讀取資料,模擬 Repository 回傳資料給 Service 進行測試。

1、先將 DB 資料匯成 CSV 檔。

將我們要匯出的資料使用 MSSQL SELECT出來。

對左上方空白的地方點選滑鼠右鍵,選擇儲存結果。

將檔案儲存到我們測試專案地方,建立一個 TestData 資料夾存放。

記得要到專案內,將要測試的 CSV 檔案屬性改成永遠複製。

2、安裝 Nuget 套件。

3、修改測試方法。

首先在測試 Class 加上一個 Attribute。

[DeploymentItem(@"TestData\AccountData.csv")]

接著我們就可以在要使用的地方這麼做。

        [TestMethod]
        [Owner("Sian")]
        [TestCategory("AccountServiceTest")]
        [TestProperty("AccountServiceTest", "GetAccount")]
        public void GetAccount_Account有資料_應回傳正確資訊使用CSV測試()
        {
            //arrange
            var sourceData = new List<AccountDataModel>();
            using (var sr = new StreamReader(@"AccountData.csv"))
            using (var reader = new CsvReader(sr, CultureInfo.InvariantCulture))
            {
                 var records = reader.GetRecords<AccountDataModel>();
                 sourceData.AddRange(records);
            }

            var sut = this.GetSystemUnderTest();
            this._accountRepository.GetAccount("test2").Returns(sourceData.FirstOrDefault(x => x.Account == "test2"));
            var expect = this._mapper.Map<AccountDto>(sourceData.FirstOrDefault(x => x.Account == "test2"));
            expect.Phone = "098812****";

            //act
            var actual = sut.GetAccount("test2");

            //assert
            actual.Should().BeEquivalentTo(expect);
        }

結果:

我們也可以做成一個私有方法,讓該類別的測試共同使用減少重複的程式碼。

        /// <summary>
        /// 取得AccountData
        /// </summary>
        /// <returns></returns>
        private IEnumerable<AccountDataModel> GetAccountData()
        {
            var sourceData = new List<AccountDataModel>();
            using (var sr = new StreamReader(@"AccountData.csv"))
            using (var reader = new CsvReader(sr, CultureInfo.InvariantCulture))
            {
                var records = reader.GetRecords<AccountDataModel>();
                sourceData.AddRange(records);
            }

            return sourceData;
        }

修改使用的地方。

        [TestMethod]
        [Owner("Sian")]
        [TestCategory("AccountServiceTest")]
        [TestProperty("AccountServiceTest", "GetAccount")]
        public void GetAccount_Account有資料_應回傳正確資訊使用CSV測試()
        {
            //arrange
            var sourceData = this.GetAccountData();
            var returnData = sourceData.FirstOrDefault(x => x.Account == "test2");
            var sut = this.GetSystemUnderTest();
            this._accountRepository.GetAccount("test2").Returns(returnData);
            var expect = this._mapper.Map<AccountDto>(returnData);
            expect.Phone = "098812****";

            //act
            var actual = sut.GetAccount("test2");

            //assert
            actual.Should().BeEquivalentTo(expect);
        }

AutoFixture

接下來介紹,AutoFixture,也是在測試時需要手捏 Model 的好用工具。

先到 Nuget 安裝 AutoFixture。

接下來到我們單元測試的地方修改一下測試方法。
我們拿 AddAccount_Account 為空_應回傳 Exception 作為示範。
原本的做法。

        [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("請檢查輸入欄位,缺一不可!");
        }

使用 AutoFixture 製造傳遞 Model 的作法。

        [TestMethod]
        [Owner("Sian")]
        [TestCategory("AccountServiceTest")]
        [TestProperty("AccountServiceTest", "AddAccount")]
        public void AddAccount_Account為空使用AutoFixture_應回傳Exception()
        {
            //assert
            var sut = this.GetSystemUnderTest();
            var fixture = new Fixture();
            var info = fixture.Build<AccountInfoModel>()
                .Without(x => x.Account)
                .Create();

            //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_密碼長度低於6碼使用AutoFixture_應回傳錯誤訊息()
        {
            //assert
            var sut = this.GetSystemUnderTest();
            var fixture = new Fixture();
            var info = fixture.Build<AccountInfoModel>()
                .With(x => x.Password,"1234")
                .Create();

            var expect = new ResultDto
            {
                Success = false,
                Message = "使用者密碼長度不可低於6碼!"
            };

            //act
            var actual = sut.AddAccount(info);

            //arrange
            actual.Should().BeEquivalentTo(expect);
        }

結果:

遇到 List 資料時可以這麼做。

        [TestMethod]
        [Owner("Sian")]
        [TestCategory("AccountServiceTest")]
        [TestProperty("AccountServiceTest", "GetAccountList")]
        public void GetAccountList_有資料使用AutoFixture_應回傳正確結果()
        {
            //arrange
            var fixture = new Fixture();
            var data = fixture.Build<AccountDataModel>()
                .With(x => x.ModifyDate , DateTime.Now)
                .With(x => x.CreateDate , DateTime.Now)
                .With(x => x.Phone , "0918777777")
                .CreateMany();

            var sut = this.GetSystemUnderTest();
            this._accountRepository.GetAccountList().Returns(data);

            var expect = this._mapper.Map<IEnumerable<AccountDto>>(data);
            expect.ToList().ForEach(x => x.Phone = "091877****");

            //act
            var actual = sut.GetAccountList();

            //assert
            actual.Should().BeEquivalentTo(expect);
        }

結果:

還有一些進階使用方法,可以參考大神示範,這邊就不獻醜了XD
mrkt 使用 AutoFixture 產生指定範圍的隨機數值
mrkt 使用 AutoFixture 產生指定範圍的隨機數值 - 設定屬性名稱與範圍
mrkt 使用 AutoFixture 產生指定範圍的隨機數值 - Customize, ICustomization

這邊一樣會把示範範例放在 GitHub 歡迎取用。

參考連結

CsvHelper

AutoFixture