延續 Day09 實作官網 ASP.NET Core 教學我們把專案加入 xUnit Test
測試架構為 xUnit。 物件模擬架構為 Moq。
在專案目錄下加入 tests 資料夾,增加 xUnit 專案
dotnet new xunit -o Tests
安裝套件
dotnet add package Microsoft.EntityFrameworkCore.InMemory
dotnet add package Microsoft.AspNetCore.TestHost
dotnet add package Moq
dotnet add package Newtonsoft.Json
dotnet add package System.Diagnostics.TraceSource
dotnet add package System.Net.Http
增加參考
dotnet add ./Tests.csproj reference ../PellokITHome.csproj  
創建 Utilities
mkdir Utilities
touch Utilities/Utilities.cs
Utilities.cs的內容如下:
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using PellokITHome.Data;
namespace Tests
{
    public static class Utilities
    {
        #region snippet1
        public static DbContextOptions<PellokITHomeContext> TestDbContextOptions()
        {
            // Create a new service provider to create a new in-memory database.
            var serviceProvider = new ServiceCollection()
                .AddEntityFrameworkInMemoryDatabase()
                .BuildServiceProvider();
            // Create a new options instance using an in-memory database and 
            // IServiceProvider that the context should resolve all of its 
            // services from.
            var builder = new DbContextOptionsBuilder<PellokITHomeContext>()
                .UseInMemoryDatabase("InMemoryDb")
                .UseInternalServiceProvider(serviceProvider);
            return builder.Options;
        }
        #endregion
    }
}
創建資料測試
mkdir UnitTests
touch UnitTests/DataAccessLayerTest.cs
內容如下:
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Xunit;
using PellokITHome.Data;
using PellokITHome.Models;
namespace Tests.UnitTests
{
    public class DataAccessLayerTest
    {
        [Fact]
        public async Task GetMessagesAsync_MessagesAreReturned()
        {
            using (var db = new PellokITHomeContext(Utilities.TestDbContextOptions()))
            {
                // Arrange
                var expectedMessages = PellokITHomeContext.GetSeedingArticles();
                await db.AddRangeAsync(expectedMessages);
                await db.SaveChangesAsync();
                // Act
                var result = await db.GetArticlesAsync();
                // Assert
                var actualMessages = Assert.IsAssignableFrom<List<Article>>(result);
                Assert.Equal(
                    expectedMessages.OrderBy(m => m.ID).Select(m => m.Title), 
                    actualMessages.OrderBy(m => m.ID).Select(m => m.Title));
            }
        }
        [Fact]
        public async Task AddMessageAsync_MessageIsAdded()
        {
            using (var db = new PellokITHomeContext(Utilities.TestDbContextOptions()))
            {
                // Arrange
                var recId = 10;
                var expectedMessage = new Article() { ID = recId, Title = "Message" };
                // Act
                await db.AddArticleAsync(expectedMessage);
                // Assert
                var actualMessage = await db.FindAsync<Article>(recId);
                Assert.Equal(expectedMessage, actualMessage);
            }
        }
        [Fact]
        public async Task DeleteAllMessagesAsync_MessagesAreDeleted()
        {
            using (var db = new PellokITHomeContext(Utilities.TestDbContextOptions()))
            {
                // Arrange
                var seedMessages = PellokITHomeContext.GetSeedingArticles();
                await db.AddRangeAsync(seedMessages);
                await db.SaveChangesAsync();
                // Act
                await db.DeleteAllArticlesAsync();
                // Assert
                Assert.Empty(await db.Articles.AsNoTracking().ToListAsync());
            }
        }
        [Fact]
        public async Task DeleteMessageAsync_MessageIsDeleted_WhenMessageIsFound()
        {
            using (var db = new PellokITHomeContext(Utilities.TestDbContextOptions()))
            {
                #region snippet1
                // Arrange
                var seedMessages = PellokITHomeContext.GetSeedingArticles();
                await db.AddRangeAsync(seedMessages);
                await db.SaveChangesAsync();
                var recId = 1;
                var expectedMessages = 
                    seedMessages.Where(message => message.ID != recId).ToList();
                #endregion
                #region snippet2
                // Act
                await db.DeleteArticleAsync(recId);
                #endregion
                #region snippet3
                // Assert
                var actualMessages = await db.Articles.AsNoTracking().ToListAsync();
                Assert.Equal(
                    expectedMessages.OrderBy(m => m.ID).Select(m => m.Title), 
                    actualMessages.OrderBy(m => m.ID).Select(m => m.Title));
                #endregion
            }
        }
        #region snippet4
        [Fact]
        public async Task DeleteMessageAsync_NoMessageIsDeleted_WhenMessageIsNotFound()
        {
            using (var db = new PellokITHomeContext(Utilities.TestDbContextOptions()))
            {
                // Arrange
                var expectedMessages = PellokITHomeContext.GetSeedingArticles();
                await db.AddRangeAsync(expectedMessages);
                await db.SaveChangesAsync();
                var recId = 4;
                // Act
                try
                {
                    await db.DeleteArticleAsync(recId);
                }
                catch
                {
                    // recId doesn't exist
                }
                // Assert
                var actualMessages = await db.Articles.AsNoTracking().ToListAsync();
                Assert.Equal(
                    expectedMessages.OrderBy(m => m.ID).Select(m => m.Title), 
                    actualMessages.OrderBy(m => m.ID).Select(m => m.Title));
            }
        }
        #endregion
    }
}
修改PellokITHomeContext.cs檔案
using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using PellokITHome.Models;
namespace PellokITHome.Data
{
    public class PellokITHomeContext : DbContext
    {
        public PellokITHomeContext (
            DbContextOptions<PellokITHomeContext> options)
            : base(options)
        {
        }
        public DbSet<PellokITHome.Models.Article> Articles { get; set; }
        #region snippet1
        public async virtual Task<List<Article>> GetArticlesAsync()
        {
            return await Articles
                .OrderBy(Article => Article.Title)
                .AsNoTracking()
                .ToListAsync();
        }
        #endregion
        #region snippet2
        public async virtual Task AddArticleAsync(Article Article)
        {
            await Articles.AddAsync(Article);
            await SaveChangesAsync();
        }
        #endregion
        #region snippet3
        public async virtual Task DeleteAllArticlesAsync()
        {
            foreach (Article Article in Articles)
            {
                Articles.Remove(Article);
            }
            
            await SaveChangesAsync();
        }
        #endregion
        #region snippet4
        public async virtual Task DeleteArticleAsync(int id)
        {
            var Article = await Articles.FindAsync(id);
            if (Article != null)
            {
                Articles.Remove(Article);
                await SaveChangesAsync();
            }
        }
        #endregion
        public void Initialize()
        {
            Articles.AddRange(GetSeedingArticles());
            SaveChanges();
        }
        public static List<Article> GetSeedingArticles()
        {
            return new List<Article>()
            {
                new Article(){ Title = "Day01 Azure 的自我修煉" },
                new Article(){ Title = "Day02 申請Azure帳號" },
                new Article(){ Title = "Day03 Resource Group 資源群組" }
            };
        }
    }
}
修改以上內容後,需要重建DB,確認專案沒有問題,執行正常。
dotnet test

合併兩個專案為一個解決方案
# 創建方案
dotnet new sln -o PellokITHomePipeline
# 到方案目錄下
cd PellokITHomePipeline
# 複製專案
cp -r ../PellokITHome .
# 移除專案暫存檔
rm -rf PellokITHome/.git
rm -rf PellokITHome/.vscode
rm -rf PellokITHome/obj
rm -rf PellokITHome/bin
# 複製專案
cp -r ../Tests .
# 移除專案暫存檔
rm -rf Tests/.git
rm -rf Tests/.vscode
rm -rf Tests/obj
rm -rf Tests/bin
# 加入專案到方案
dotnet sln add ./PellokITHome/PellokITHome.csproj
dotnet sln add ./Tests/Tests.csproj

新增 .gitignore
*/bin/*
*/obj/*
*/.vscode/*
*.db
提交索引、提交版本
mv PellokITHome/.gitignore .
git init
git status
git commit -m "first commit"

code -r .
# 測試
dotnet test
上一篇 Day20 實作 Dotnet Test 測試範例
下一篇 Day22 整合CI測試到 Azure Pipeline 服務