@elf-express/admin-net-mcp 1.0.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +83 -88
- package/knowledge/Furion_Teaching_Manual/04-1-/351/205/215/347/275/256.md +442 -0
- package/knowledge/Furion_Teaching_Manual/04-2-/351/201/270/351/240/205.md +363 -0
- package/knowledge/Furion_Teaching_Manual/05-1-/345/213/225/346/205/213WebAPI.md +825 -0
- package/knowledge/Furion_Teaching_Manual/05-2-HttpContext.md +217 -0
- package/knowledge/Furion_Teaching_Manual/05-3-/347/257/251/351/201/270/345/231/250/346/224/224/346/210/252/345/231/250AOP.md +581 -0
- package/knowledge/Furion_Teaching_Manual/05-4-/350/253/213/346/261/202/347/250/275/346/240/270/346/227/245/350/252/214.md +129 -0
- package/knowledge/Furion_Teaching_Manual/05-5-/344/270/255/344/273/213/350/273/237/351/253/224Middleware.md +328 -0
- package/knowledge/Furion_Teaching_Manual/05-6-Vue-React-Angular/344/273/213/351/235/242/344/273/243/347/220/206.md +317 -0
- package/knowledge/Furion_Teaching_Manual/06-1/350/246/217/347/257/204/345/214/226/346/216/245/345/217/243.md +1458 -0
- package/knowledge/Furion_Teaching_Manual/06-2/347/254/254/344/270/211/346/226/271API_Scalar.md +91 -0
- package/knowledge/Furion_Teaching_Manual/07-/345/217/213/345/245/275/344/276/213/345/244/226/350/231/225/347/220/206.md +511 -0
- package/knowledge/Furion_Teaching_Manual/08-1-/350/263/207/346/226/231/351/251/227/350/255/211/345/237/272/347/244/216/344/275/277/347/224/250.md +587 -0
- package/knowledge/Furion_Teaching_Manual/10-1-SqlSugar/346/225/264/345/220/210.md +336 -0
- package/knowledge/Furion_Teaching_Manual/11-SaaS /345/244/232/347/247/237/346/210/266/347/255/206/350/250/230.md" +271 -0
- package/knowledge/Furion_Teaching_Manual/12-furion-dependency-injection.md +408 -0
- package/knowledge/Furion_Teaching_Manual/13-/347/211/251/344/273/266/350/263/207/346/226/231/346/230/240/345/260/204/357/274/210Mapster/357/274/211.md +162 -0
- package/knowledge/Furion_Teaching_Manual/14-/345/210/206/345/270/203/345/274/217/347/274/223/345/255/230.md +311 -0
- package/knowledge/Furion_Teaching_Manual/15-/345/256/211/345/205/250/351/211/264/346/235/203.md +832 -0
- package/knowledge/Furion_Teaching_Manual/17-/346/252/242/350/246/226/347/257/204/346/234/254/345/274/225/346/223/216.md +327 -0
- package/knowledge/Furion_Teaching_Manual/18-/346/227/245/350/252/214/350/250/230/351/214/204.md +639 -0
- package/knowledge/Furion_Teaching_Manual/19-1-HTTP/351/201/240/347/253/257/350/253/213/346/261/202/345/237/272/347/244/216/344/275/277/347/224/250.md +621 -0
- package/knowledge/Furion_Teaching_Manual/19-2-HTTP/351/201/240/347/253/257/350/253/213/346/261/202/351/200/262/351/232/216/346/214/207/345/215/227.md +928 -0
- package/knowledge/Furion_Teaching_Manual/19-3-HTTP/351/201/240/347/253/257/350/253/213/346/261/202/345/270/270/350/246/213/345/225/217/351/241/214.md +362 -0
- package/knowledge/Furion_Teaching_Manual/20-/350/263/207/346/226/231/345/212/240/350/247/243/345/257/206.md +286 -0
- package/knowledge/Furion_Teaching_Manual/20-/351/231/204/351/214/204-KSort/350/263/207/346/226/231/347/260/275/345/220/215/345/256/214/346/225/264/345/216/237/345/247/213/347/242/274.md +305 -0
- package/knowledge/Furion_Teaching_Manual/21-/345/205/250/347/220/203/345/214/226/345/222/214/346/234/254/345/234/260/345/214/226.md +342 -0
- package/knowledge/Furion_Teaching_Manual/22-/344/272/213/344/273/266/345/214/257/346/265/201/346/216/222EventBus.md +448 -0
- package/knowledge/Furion_Teaching_Manual/23-JSON/345/272/217/345/210/227/345/214/226.md +340 -0
- package/knowledge/Furion_Teaching_Manual/24-/345/215/263/346/231/202/351/200/232/350/250/212SignalR.md +247 -0
- package/knowledge/Furion_Teaching_Manual/25-/350/274/224/345/212/251/350/247/222/350/211/262/346/234/215/345/213/231WorkerService.md +295 -0
- package/knowledge/Furion_Teaching_Manual/26-1-/346/216/222/347/250/213/344/275/234/346/245/255/345/256/232/346/231/202/344/273/273/345/213/231.md +631 -0
- package/knowledge/Furion_Teaching_Manual/26-2-Cron/350/241/250/351/201/224/345/274/217.md +203 -0
- package/knowledge/Furion_Teaching_Manual/26-3-/344/273/273/345/213/231/344/275/207/345/210/227TaskQueue.md +215 -0
- package/knowledge/Furion_Teaching_Manual/27-/345/210/206/346/225/243/345/274/217ID/347/224/237/346/210/220.md +86 -0
- package/knowledge/Furion_Teaching_Manual/28-/346/250/241/347/265/204/345/214/226/351/226/213/347/231/274.md +68 -0
- package/knowledge/Furion_Teaching_Manual/29-/346/265/201/350/256/212/347/211/251/344/273/266Clay.md +313 -0
- package/knowledge/Furion_Teaching_Manual/30-/350/204/253/346/225/217/350/231/225/347/220/206/357/274/210Sensitive Detection).md" +168 -0
- package/knowledge/Furion_Teaching_Manual/32-/346/234/203/350/251/261/345/222/214/347/213/200/346/205/213/347/256/241/347/220/206.md +147 -0
- package/knowledge/Furion_Teaching_Manual/33-IPC/347/250/213/345/272/217/351/200/232/350/250/212.md +98 -0
- package/knowledge/Furion_Teaching_Manual/34-2-Docker/351/203/250/347/275/262.md +313 -0
- package/knowledge/Furion_Teaching_Manual/34-3-Nginx/351/203/250/347/275/262.md +157 -0
- package/knowledge/Furion_Teaching_Manual/34-8-WindowsService/351/203/250/347/275/262.md +112 -0
- package/knowledge/Furion_Teaching_Manual/35-1-Docker/347/222/260/345/242/203/346/214/201/347/272/214/351/203/250/347/275/262Jenkins.md +169 -0
- package/knowledge/Furion_Teaching_Manual/36-1-/345/226/256/345/205/203/346/270/254/350/251/246/346/225/264/345/220/210/346/270/254/350/251/246.md +275 -0
- package/knowledge/Furion_Teaching_Manual/36-3-/345/237/272/346/272/226/346/270/254/350/251/246Benchmarking.md +80 -0
- package/knowledge/attributes.md +153 -0
- package/knowledge/config.md +147 -0
- package/knowledge/entity.md +115 -0
- package/knowledge/event.md +124 -0
- package/knowledge/plugin.md +136 -0
- package/knowledge/service.md +146 -0
- package/knowledge/sqlsugar.md +172 -0
- package/knowledge/vue-typescript.md +338 -0
- package/package.json +3 -2
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# 35.1 Docker 環境持續部署(Jenkins)
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## 35.1.1 概述
|
|
6
|
+
|
|
7
|
+
利用包含 .NET 環境的 Jenkins Docker 映像檔,實現持續化部署。
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 35.1.2 安裝 Docker 版 Jenkins
|
|
12
|
+
|
|
13
|
+
標準 `jenkins:lts` 映像檔無法執行 `dotnet` 命令,需自行建置包含 .NET SDK 的映像檔。
|
|
14
|
+
|
|
15
|
+
### Dockerfile
|
|
16
|
+
|
|
17
|
+
```dockerfile
|
|
18
|
+
FROM jenkins/jenkins:lts
|
|
19
|
+
USER root
|
|
20
|
+
WORKDIR /dotnet
|
|
21
|
+
RUN apt-get update && apt-get install -y wget && rm -rf /var/lib/apt/lists/*
|
|
22
|
+
RUN wget -O dotnet.tar.gz https://download.visualstudio.microsoft.com/download/pr/.../dotnet-sdk-5.0.100-linux-x64.tar.gz
|
|
23
|
+
RUN tar zxf dotnet.tar.gz -C ./ && rm -rf dotnet.tar.gz
|
|
24
|
+
ENV PATH="${PATH}:/dotnet:/var/jenkins_home/.dotnet/tools"
|
|
25
|
+
ENV DOTNET_ROOT="/dotnet"
|
|
26
|
+
RUN apt update -y && apt install icu-devtools vim zip unzip -y
|
|
27
|
+
RUN usermod -a -G root jenkins
|
|
28
|
+
USER jenkins
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
> SDK 下載連結請至微軟官網取得對應版本:https://dotnet.microsoft.com/download
|
|
32
|
+
|
|
33
|
+
### 建置與執行
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
# 建置映像檔
|
|
37
|
+
docker build -t jenkins:dotnet .
|
|
38
|
+
|
|
39
|
+
# 執行容器
|
|
40
|
+
docker run -d -p 8080:8080 -p 50000:50000 --name mjenkins \
|
|
41
|
+
--privileged=true --restart always -u root \
|
|
42
|
+
-e TZ="Asia/Shanghai" \
|
|
43
|
+
-v /mudata/jenkins:/var/jenkins_home \
|
|
44
|
+
-v /usr/bin/docker:/usr/bin/docker \
|
|
45
|
+
-v /var/run/docker.sock:/var/run/docker.sock \
|
|
46
|
+
-v /mudata/webroot/:/mudata/webroot \
|
|
47
|
+
jenkins:dotnet
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 35.1.3 自動化部署(本地)
|
|
53
|
+
|
|
54
|
+
### Shell 腳本
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
#!/bin/bash
|
|
58
|
+
# Jenkins 建置腳本(測試服)
|
|
59
|
+
|
|
60
|
+
solutionName=YourProject.Web.Entry
|
|
61
|
+
containerName=yourcontainer
|
|
62
|
+
port=9994
|
|
63
|
+
csprojDir=${solutionName}/${solutionName}.csproj
|
|
64
|
+
webDir=/mudata/webroot/jenkins/publish/webapp
|
|
65
|
+
|
|
66
|
+
dotnet restore
|
|
67
|
+
|
|
68
|
+
# 清空並發佈
|
|
69
|
+
rm -rf ${webDir}/${JOB_NAME}/*
|
|
70
|
+
dotnet publish ${JENKINS_HOME}/workspace/${JOB_NAME}/${csprojDir} \
|
|
71
|
+
-c Release -o ${webDir}/${JOB_NAME} /p:Version=1.0.${BUILD_NUMBER}
|
|
72
|
+
|
|
73
|
+
# 停止並移除舊容器
|
|
74
|
+
CID=$(docker ps -a | grep "${containerName}" | awk '{print $1}')
|
|
75
|
+
if [ "$CID" != "" ]; then
|
|
76
|
+
docker stop ${containerName}
|
|
77
|
+
docker rm ${containerName}
|
|
78
|
+
docker rmi ${containerName}
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
# 重新建置並執行
|
|
82
|
+
docker build -t ${containerName} ${webDir}/${JOB_NAME}/.
|
|
83
|
+
docker run --name ${containerName} --restart=always -d \
|
|
84
|
+
-p ${port}:${port} -v /etc/localtime:/etc/localtime:ro ${containerName}
|
|
85
|
+
|
|
86
|
+
echo "success!"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
> 流程:拉取原始碼 → `dotnet publish` → `docker build` → `docker run`。可設定分支合併後自動觸發建置。
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## 35.1.4 自動化遠端部署
|
|
94
|
+
|
|
95
|
+
### 前置條件
|
|
96
|
+
|
|
97
|
+
安裝 Jenkins 外掛:**Publish Over SSH**,並在「系統管理 → Publish over SSH」設定 SSH 伺服器。
|
|
98
|
+
|
|
99
|
+
### 建置腳本(Jenkins 端)
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
#!/bin/bash
|
|
103
|
+
# 發佈並打包
|
|
104
|
+
|
|
105
|
+
solutionName=YourProject.Web.Entry
|
|
106
|
+
csprojDir=/${solutionName}/${solutionName}.csproj
|
|
107
|
+
webDir=/mudata/webroot/jenkins/publish/webapp
|
|
108
|
+
|
|
109
|
+
dotnet restore
|
|
110
|
+
|
|
111
|
+
rm -rf ${webDir}/${JOB_NAME}/*
|
|
112
|
+
dotnet publish ${JENKINS_HOME}/workspace/${JOB_NAME}/${csprojDir} \
|
|
113
|
+
-c Release -o ${webDir}/${JOB_NAME} /p:Version=1.0.${BUILD_NUMBER}
|
|
114
|
+
|
|
115
|
+
# 打包
|
|
116
|
+
rm -rf ${JENKINS_HOME}/workspace/${JOB_NAME}/publish
|
|
117
|
+
mkdir ${JENKINS_HOME}/workspace/${JOB_NAME}/publish
|
|
118
|
+
tar -czvf ${JENKINS_HOME}/workspace/${JOB_NAME}/publish/${JOB_NAME}.${BUILD_NUMBER}.tar.gz \
|
|
119
|
+
-C ${webDir}/${JOB_NAME} .
|
|
120
|
+
|
|
121
|
+
echo "success!"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### 建置後操作(Send Build artifacts over SSH)
|
|
125
|
+
|
|
126
|
+
| 設定 | 值 |
|
|
127
|
+
|------|------|
|
|
128
|
+
| Source files | `publish/` |
|
|
129
|
+
| Remote directory | `/mudata/webroot/publish/` |
|
|
130
|
+
| Exec command | `bash /mudata/shell/publish.sh ${JOB_NAME} yourcontainer ${JOB_NAME}.${BUILD_NUMBER} 9994` |
|
|
131
|
+
|
|
132
|
+
> Source files 必須是 workspace 下的路徑。Jenkins 環境變數都可使用。
|
|
133
|
+
|
|
134
|
+
### 遠端執行腳本(publish.sh)
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
#!/bin/bash
|
|
138
|
+
# 遠端伺服器部署腳本
|
|
139
|
+
|
|
140
|
+
solutionName=$1
|
|
141
|
+
containerName=$2
|
|
142
|
+
filename=$3
|
|
143
|
+
port=$4
|
|
144
|
+
baseDir=/mudata/webroot/publish
|
|
145
|
+
webDir=${baseDir}/publish/${filename}
|
|
146
|
+
|
|
147
|
+
# 解壓
|
|
148
|
+
rm -rf ${webDir} && mkdir ${webDir}
|
|
149
|
+
tar -zxvf ${baseDir}/publish/${filename}.tar.gz -C ${webDir}/
|
|
150
|
+
|
|
151
|
+
# 替換正式環境設定
|
|
152
|
+
rm -f ${webDir}/appsettings.json
|
|
153
|
+
mv ${webDir}/appsettings.Prod.json ${webDir}/appsettings.json
|
|
154
|
+
|
|
155
|
+
# 停止並移除舊容器
|
|
156
|
+
CID=$(docker ps -a | grep "${containerName}" | awk '{print $1}')
|
|
157
|
+
if [ "$CID" != "" ]; then
|
|
158
|
+
docker stop ${containerName}
|
|
159
|
+
docker rm ${containerName}
|
|
160
|
+
docker rmi ${containerName}
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
# 建置並執行
|
|
164
|
+
cd ${webDir}/ && docker build -t ${containerName} .
|
|
165
|
+
docker run --name ${containerName} --restart=always -d \
|
|
166
|
+
-p ${port}:${port} -v /etc/localtime:/etc/localtime:ro ${containerName}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
> 流程:Jenkins 發佈打包 → SSH 傳送到遠端 → 解壓 → 替換設定檔 → `docker build` → `docker run`。每次建置都帶版本號。
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
# 36.1 單元測試 / 整合測試
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## 36.1.1 概述
|
|
6
|
+
|
|
7
|
+
單元測試是對軟體中最小可測試單元進行檢查和驗證的過程。好處包括:消滅低級錯誤、找出潛在 bug、上線前保證、重構機會、重新 review 程式碼。
|
|
8
|
+
|
|
9
|
+
測試類型:基於 API 介面測試(白盒 + 淺度黑盒)、基於專案程式碼測試(深度白盒)。
|
|
10
|
+
|
|
11
|
+
> Furion 框架使用 **xUnit** 進行單元測試。
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## 36.1.5 第一個範例
|
|
16
|
+
|
|
17
|
+
### 建立 xUnit 專案並編寫測試
|
|
18
|
+
|
|
19
|
+
```csharp
|
|
20
|
+
using Xunit;
|
|
21
|
+
|
|
22
|
+
public class UnitTest1
|
|
23
|
+
{
|
|
24
|
+
[Fact]
|
|
25
|
+
public void Test1()
|
|
26
|
+
{
|
|
27
|
+
Assert.Equal(2, 1 + 1);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
> 標記 `[Fact]` 的方法為測試方法,使用 `Assert` 類進行斷言。
|
|
33
|
+
|
|
34
|
+
### 帶參數的測試
|
|
35
|
+
|
|
36
|
+
```csharp
|
|
37
|
+
[Theory]
|
|
38
|
+
[InlineData(1, 2)]
|
|
39
|
+
[InlineData(3, 4)]
|
|
40
|
+
[InlineData(5, 7)]
|
|
41
|
+
public void 帶參數測試(int i, int j)
|
|
42
|
+
{
|
|
43
|
+
Assert.NotEqual(0, (i + j) % 2);
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## 36.1.6 整合 Furion
|
|
50
|
+
|
|
51
|
+
### 安裝套件
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
# 完整版
|
|
55
|
+
dotnet add package Furion.Xunit
|
|
56
|
+
|
|
57
|
+
# 純淨版
|
|
58
|
+
dotnet add package Furion.Pure.Xunit
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
> `Furion.Xunit` 已包含 `Furion`,無需重複安裝。請確保已安裝 `xunit` 套件。
|
|
62
|
+
|
|
63
|
+
### 新增初始設定類 TestProgram.cs
|
|
64
|
+
|
|
65
|
+
```csharp
|
|
66
|
+
using Furion.Xunit;
|
|
67
|
+
using Xunit.Abstractions;
|
|
68
|
+
using Xunit.Sdk;
|
|
69
|
+
|
|
70
|
+
[assembly: TestFramework("TestProject1.TestProgram", "TestProject1")]
|
|
71
|
+
|
|
72
|
+
namespace TestProject1;
|
|
73
|
+
|
|
74
|
+
public class TestProgram : TestStartup
|
|
75
|
+
{
|
|
76
|
+
public TestProgram(IMessageSink messageSink) : base(messageSink)
|
|
77
|
+
{
|
|
78
|
+
Serve.RunNative(services =>
|
|
79
|
+
{
|
|
80
|
+
services.AddHttpRemote(); // 註冊所需服務
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
> 若出現 `did not have matching fixture data` 錯誤,可設定 `urls: Serve.IdleHost.Urls`。
|
|
87
|
+
|
|
88
|
+
### 編寫測試方法
|
|
89
|
+
|
|
90
|
+
```csharp
|
|
91
|
+
[Fact]
|
|
92
|
+
public async Task 測試請求百度()
|
|
93
|
+
{
|
|
94
|
+
var res = await _httpRemoteService.GetAsync("https://www.baidu.com");
|
|
95
|
+
Assert.True(res.IsSuccessStatusCode);
|
|
96
|
+
}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## 36.1.8 相依性注入
|
|
102
|
+
|
|
103
|
+
### 建構子注入
|
|
104
|
+
|
|
105
|
+
```csharp
|
|
106
|
+
public class UnitTest1
|
|
107
|
+
{
|
|
108
|
+
private readonly ICalcService _calcService;
|
|
109
|
+
|
|
110
|
+
public UnitTest1(ICalcService calcService)
|
|
111
|
+
{
|
|
112
|
+
_calcService = calcService;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
[Fact]
|
|
116
|
+
public void 測試加法()
|
|
117
|
+
{
|
|
118
|
+
Assert.Equal(3, _calcService.Plus(1, 2));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
> Furion 會在建立測試實例時建立 `IServiceScope`,所有測試案例完成後自動 `Dispose`。
|
|
124
|
+
|
|
125
|
+
### 輸出日誌
|
|
126
|
+
|
|
127
|
+
```csharp
|
|
128
|
+
public class UnitTest1
|
|
129
|
+
{
|
|
130
|
+
private readonly ITestOutputHelper Output;
|
|
131
|
+
|
|
132
|
+
public UnitTest1(ITestOutputHelper tempOutput) => Output = tempOutput;
|
|
133
|
+
|
|
134
|
+
[Fact]
|
|
135
|
+
public void Test()
|
|
136
|
+
{
|
|
137
|
+
Output.WriteLine("我是 Furion");
|
|
138
|
+
Assert.NotEqual("Furion", "Fur");
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### 三種 Fixture 注入方式
|
|
144
|
+
|
|
145
|
+
| 方式 | 有效範圍 | 用法 |
|
|
146
|
+
|------|---------|------|
|
|
147
|
+
| `[AssemblyFixture(typeof(T))]` | 全域所有測試類 | 在 `TestProgram.cs` 頂部標記 |
|
|
148
|
+
| `IClassFixture<T>` | 單個測試類 | 測試類實作該介面 |
|
|
149
|
+
| `ICollectionFixture<T>` + `[Collection]` | 多個測試類 | 定義 Collection 配置器 |
|
|
150
|
+
|
|
151
|
+
**AssemblyFixture 範例:**
|
|
152
|
+
|
|
153
|
+
```csharp
|
|
154
|
+
[assembly: AssemblyFixture(typeof(MyAssemblyFixture))]
|
|
155
|
+
|
|
156
|
+
public class MyAssemblyFixture : IDisposable
|
|
157
|
+
{
|
|
158
|
+
public MyAssemblyFixture() { }
|
|
159
|
+
public void Dispose() { }
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**IClassFixture 範例:**
|
|
164
|
+
|
|
165
|
+
```csharp
|
|
166
|
+
public class UnitTest1 : IClassFixture<MyClassFixture>
|
|
167
|
+
{
|
|
168
|
+
public UnitTest1(ICalcService calcService, MyClassFixture fixture) { }
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
**ICollectionFixture 範例:**
|
|
173
|
+
|
|
174
|
+
```csharp
|
|
175
|
+
[CollectionDefinition("MyCollection")]
|
|
176
|
+
public class MyCollection : ICollectionFixture<MyCollectionFixture> { }
|
|
177
|
+
|
|
178
|
+
[Collection("MyCollection")]
|
|
179
|
+
public class UnitTest1
|
|
180
|
+
{
|
|
181
|
+
public UnitTest1(MyCollectionFixture collectionFixture) { }
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## 36.1.9 Web 整合測試
|
|
188
|
+
|
|
189
|
+
### 方式一:對現有專案進行整合測試
|
|
190
|
+
|
|
191
|
+
1. 安裝 `Microsoft.AspNetCore.Mvc.Testing`
|
|
192
|
+
2. 設定 Web 專案:
|
|
193
|
+
|
|
194
|
+
**.NET 5**:新增 `FakeStartup.cs`(空類別)
|
|
195
|
+
|
|
196
|
+
**.NET 6+**:編輯 `.csproj` 新增 `<InternalsVisibleTo Include="測試專案名" />`,並在 `Program.cs` 加入 `public partial class Program { }`
|
|
197
|
+
|
|
198
|
+
3. 編寫測試:
|
|
199
|
+
|
|
200
|
+
```csharp
|
|
201
|
+
// .NET 5
|
|
202
|
+
public class UnitTest1 : IClassFixture<WebApplicationFactory<WebApplication1.FakeStartup>>
|
|
203
|
+
{
|
|
204
|
+
private readonly WebApplicationFactory<WebApplication1.FakeStartup> _factory;
|
|
205
|
+
|
|
206
|
+
public UnitTest1(WebApplicationFactory<WebApplication1.FakeStartup> factory)
|
|
207
|
+
{
|
|
208
|
+
_factory = factory;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
[Theory]
|
|
212
|
+
[InlineData("/default")]
|
|
213
|
+
public async Task TestEnsureSuccessStatusCode(string url)
|
|
214
|
+
{
|
|
215
|
+
using var client = _factory.CreateClient();
|
|
216
|
+
using var response = await client.GetAsync(url);
|
|
217
|
+
response.EnsureSuccessStatusCode();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// .NET 6+:將泛型參數改為 WebApplicationFactory<Program>
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
主機額外設定(如環境變數):
|
|
225
|
+
|
|
226
|
+
```csharp
|
|
227
|
+
_factory = factory.WithWebHostBuilder(builder =>
|
|
228
|
+
{
|
|
229
|
+
builder.UseEnvironment("Stage");
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### 方式二:獨立主機方式
|
|
234
|
+
|
|
235
|
+
```csharp
|
|
236
|
+
[Fact]
|
|
237
|
+
public void Test1()
|
|
238
|
+
{
|
|
239
|
+
var builder = WebApplication.CreateBuilder();
|
|
240
|
+
builder.Services.AddScoped<IYourService, YourService>();
|
|
241
|
+
|
|
242
|
+
using var app = builder.Build();
|
|
243
|
+
app.Services.GetRequiredService<IYourService>();
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### 方式三:系統整合 / 環境 / 設定部署測試
|
|
248
|
+
|
|
249
|
+
安裝 `Microsoft.AspNetCore.TestHost`,使用 `[assembly: HostingStartup(...)]` 特性。參考微軟官方範例。
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## 36.1.10 Assert 斷言速查
|
|
254
|
+
|
|
255
|
+
常用方法(第一個參數為期望值,第二個為實際值):
|
|
256
|
+
|
|
257
|
+
| 方法 | 說明 |
|
|
258
|
+
|------|------|
|
|
259
|
+
| `Assert.Equal(expected, actual)` | 相等 |
|
|
260
|
+
| `Assert.NotEqual(expected, actual)` | 不相等 |
|
|
261
|
+
| `Assert.True(condition)` | 為 true |
|
|
262
|
+
| `Assert.False(condition)` | 為 false |
|
|
263
|
+
| `Assert.Null(object)` | 為 null |
|
|
264
|
+
| `Assert.NotNull(object)` | 不為 null |
|
|
265
|
+
| `Assert.Contains(expected, collection)` | 集合包含 |
|
|
266
|
+
| `Assert.Throws<T>(action)` | 拋出指定例外 |
|
|
267
|
+
| `Assert.IsType<T>(object)` | 型別檢查 |
|
|
268
|
+
|
|
269
|
+
> 完整 Assert 方法請查閱 xUnit 官方文件。
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## 36.1.11 單元測試覆蓋率
|
|
274
|
+
|
|
275
|
+
Visual Studio 提供內建的測試覆蓋率分析工具,可在「測試」選單中啟用。
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# 36.3 基準測試(Benchmarking)
|
|
2
|
+
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## 36.3.1 概述
|
|
6
|
+
|
|
7
|
+
基準測試是一種測量和評估軟體效能指標的活動。透過建立已知的效能水準(基準線),在系統軟硬體環境變更後重新測試,以確定變更對效能的影響。其他用途包括:測定負載極限、管理環境變化、發現潛在效能問題等。
|
|
8
|
+
|
|
9
|
+
### 特質
|
|
10
|
+
|
|
11
|
+
- **可重複性**:可進行重複測試,比較結果取得長期變化趨勢
|
|
12
|
+
- **可觀測性**:全方位監控(執行機、伺服器、資料庫)
|
|
13
|
+
- **可展示性**:直觀呈現結果(儀表板、圖表)
|
|
14
|
+
- **真實性**:反映客戶真實體驗
|
|
15
|
+
- **可執行性**:可快速定位、分析、調優
|
|
16
|
+
|
|
17
|
+
### 意義
|
|
18
|
+
|
|
19
|
+
確定系統極限、提供設定參考依據、驗收系統能力、建立效能基線。
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## 36.3.3 BenchmarkDotNet
|
|
24
|
+
|
|
25
|
+
.NET 平台的基準測試工具,可將方法轉換為基準測試,追蹤效能並產生可重複的測量結果。
|
|
26
|
+
|
|
27
|
+
### 使用方式
|
|
28
|
+
|
|
29
|
+
1. 建立**主控台應用程式**
|
|
30
|
+
2. 安裝 `BenchmarkDotNet` NuGet 套件
|
|
31
|
+
3. 編寫測試:
|
|
32
|
+
|
|
33
|
+
```csharp
|
|
34
|
+
using System;
|
|
35
|
+
using System.Security.Cryptography;
|
|
36
|
+
using BenchmarkDotNet.Attributes;
|
|
37
|
+
using BenchmarkDotNet.Running;
|
|
38
|
+
|
|
39
|
+
namespace MyBenchmarks
|
|
40
|
+
{
|
|
41
|
+
public class Md5VsSha256
|
|
42
|
+
{
|
|
43
|
+
private const int N = 10000;
|
|
44
|
+
private readonly byte[] data;
|
|
45
|
+
private readonly SHA256 sha256 = SHA256.Create();
|
|
46
|
+
private readonly MD5 md5 = MD5.Create();
|
|
47
|
+
|
|
48
|
+
public Md5VsSha256()
|
|
49
|
+
{
|
|
50
|
+
data = new byte[N];
|
|
51
|
+
new Random(42).NextBytes(data);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
[Benchmark]
|
|
55
|
+
public byte[] Sha256() => sha256.ComputeHash(data);
|
|
56
|
+
|
|
57
|
+
[Benchmark]
|
|
58
|
+
public byte[] Md5() => md5.ComputeHash(data);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
public class Program
|
|
62
|
+
{
|
|
63
|
+
public static void Main(string[] args)
|
|
64
|
+
{
|
|
65
|
+
var summary = BenchmarkRunner.Run<Md5VsSha256>();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 結果範例
|
|
72
|
+
|
|
73
|
+
| Method | Runtime | N | Mean | Ratio |
|
|
74
|
+
|--------|---------|-------|----------|-------|
|
|
75
|
+
| Sha256 | .NET 4.7.2 | 10000 | 74.509 μs | 1.00 |
|
|
76
|
+
| Sha256 | .NET Core 3.0 | 10000 | 36.049 μs | 0.49 |
|
|
77
|
+
| Md5 | .NET 4.7.2 | 10000 | 17.308 μs | 1.00 |
|
|
78
|
+
| Md5 | .NET Core 3.0 | 10000 | 15.726 μs | 0.90 |
|
|
79
|
+
|
|
80
|
+
> BenchmarkDotNet 還支援匯出各種圖表報表。
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Admin.NET 自訂 Attribute 參考
|
|
2
|
+
|
|
3
|
+
## 業務邏輯 Attribute
|
|
4
|
+
|
|
5
|
+
### [IdempotentAttribute] — 冪等請求防重複
|
|
6
|
+
```csharp
|
|
7
|
+
[IdempotentAttribute(IntervalTime = 5, Message = "請勿重複提交", ThrowBah = false)]
|
|
8
|
+
public async Task<Result> CreateOrder(OrderInput input)
|
|
9
|
+
{
|
|
10
|
+
// 5 秒內重複呼叫會返回第一次的結果(或拋出例外)
|
|
11
|
+
}
|
|
12
|
+
```
|
|
13
|
+
| 參數 | 說明 |
|
|
14
|
+
|------|------|
|
|
15
|
+
| IntervalTime | 防重複間隔秒數(預設 5) |
|
|
16
|
+
| Message | 重複時的錯誤訊息 |
|
|
17
|
+
| ThrowBah | true=拋出例外,false=返回快取結果 |
|
|
18
|
+
|
|
19
|
+
**原理**:組合 路由+UserId+參數 產生 Hash,用分散式快取鎖防重複。
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
### [DataMask] — 資料遮罩
|
|
24
|
+
```csharp
|
|
25
|
+
// 手機號碼:134****1234
|
|
26
|
+
[DataMask(3, 4)] // 從第3位開始,遮罩4個字元
|
|
27
|
+
public string PhoneNumber { get; set; }
|
|
28
|
+
|
|
29
|
+
// 身分證:110***********1234
|
|
30
|
+
[DataMask(3, 11)]
|
|
31
|
+
public string IdCard { get; set; }
|
|
32
|
+
```
|
|
33
|
+
JSON 序列化時自動套用遮罩,不影響資料庫中的原始資料。
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
### [Dict] — 字典值驗證
|
|
38
|
+
```csharp
|
|
39
|
+
// 使用 Enum 驗證
|
|
40
|
+
[Dict(SmsProviderEnum.Aliyun)]
|
|
41
|
+
public SmsProviderEnum? Provider { get; set; }
|
|
42
|
+
|
|
43
|
+
// 使用字典代碼驗證
|
|
44
|
+
[Dict("user_status", AllowNullValue = true)]
|
|
45
|
+
public string Status { get; set; }
|
|
46
|
+
```
|
|
47
|
+
| 參數 | 說明 |
|
|
48
|
+
|------|------|
|
|
49
|
+
| AllowNullValue | 是否允許 null(預設 false) |
|
|
50
|
+
| AllowEmptyStrings | 是否允許空字串(預設 false) |
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
### [OwnerUser] — 資料擁有者標記
|
|
55
|
+
```csharp
|
|
56
|
+
[OwnerUser]
|
|
57
|
+
public long? CreateUserId { get; set; }
|
|
58
|
+
```
|
|
59
|
+
標記此欄位為資料擁有者 ID,用於資料權限過濾。EntityBase 的 CreateUserId 已套用此 Attribute。
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 資料表初始化 Attribute
|
|
64
|
+
|
|
65
|
+
### 資料庫路由 Attribute
|
|
66
|
+
| Attribute | 用途 |
|
|
67
|
+
|-----------|------|
|
|
68
|
+
| `[SysTableAttribute]` | 強制使用主資料庫(不受多租戶影響) |
|
|
69
|
+
| `[TenantAttribute]` | 路由至租戶專屬資料庫 |
|
|
70
|
+
| `[LogTableAttribute]` | 路由至獨立 Log 資料庫 |
|
|
71
|
+
| `[IgnoreTableAttribute]` | SqlSugar 不建立此資料表 |
|
|
72
|
+
|
|
73
|
+
### 初始化行為 Attribute
|
|
74
|
+
| Attribute | 用途 |
|
|
75
|
+
|-----------|------|
|
|
76
|
+
| `[SeedDataAttribute]` | 此實體有對應的種子資料類別 |
|
|
77
|
+
| `[IncreTableAttribute]` | 允許增量更新資料表結構(新增欄位) |
|
|
78
|
+
| `[IncreSeedAttribute]` | 允許增量更新種子資料(新增記錄) |
|
|
79
|
+
| `[IgnoreUpdateSeedAttribute]` | 即使有 IncreTableAttribute 也跳過種子更新 |
|
|
80
|
+
| `[IgnoreUpdateSeedColumnAttribute]` | 種子更新時忽略此欄位的比對 |
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 匯入/匯出 Attribute
|
|
85
|
+
|
|
86
|
+
### [ImportDictAttribute] — 匯入時字典轉換
|
|
87
|
+
```csharp
|
|
88
|
+
[ImportDictAttribute("gender")] // Excel 匯入時,將"男/女"轉換為對應的字典 Key
|
|
89
|
+
public string Gender { get; set; }
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### [IgnoreEnumToDictAttribute] — 跳過 Enum 字典轉換
|
|
93
|
+
```csharp
|
|
94
|
+
[IgnoreEnumToDictAttribute]
|
|
95
|
+
public SomeEnum InternalStatus { get; set; } // 不加入字典轉換
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## API/回應 Attribute
|
|
101
|
+
|
|
102
|
+
### [CustomUnifyResultAttribute] — 自訂回應格式
|
|
103
|
+
```csharp
|
|
104
|
+
[CustomUnifyResultAttribute(typeof(MyResultProvider))]
|
|
105
|
+
public class SpecialApiService : IDynamicApiController, ITransient
|
|
106
|
+
{
|
|
107
|
+
// 此服務使用自訂回應格式(不使用預設的統一回應)
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### [CustomJsonPropertyAttribute] — 自訂 JSON 屬性名
|
|
112
|
+
```csharp
|
|
113
|
+
[CustomJsonPropertyAttribute("order_no")]
|
|
114
|
+
public string OrderNo { get; set; } // JSON 序列化為 "order_no"
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 常數分組 Attribute
|
|
120
|
+
|
|
121
|
+
### [ConstAttribute] — 常數分組
|
|
122
|
+
```csharp
|
|
123
|
+
[ConstAttribute("SMS供應商")]
|
|
124
|
+
public class SmsProviderConst
|
|
125
|
+
{
|
|
126
|
+
public const string Aliyun = "aliyun";
|
|
127
|
+
public const string Tencent = "tencent";
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### [EnumAttribute] — Enum 標記
|
|
132
|
+
```csharp
|
|
133
|
+
[EnumAttribute]
|
|
134
|
+
public enum OrderStatusEnum
|
|
135
|
+
{
|
|
136
|
+
[Description("待付款")] Pending = 0,
|
|
137
|
+
[Description("已付款")] Paid = 1,
|
|
138
|
+
[Description("已完成")] Completed = 2
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Attribute 目錄
|
|
145
|
+
所有自訂 Attribute 位於:`Admin.NET.Core/Attribute/`
|
|
146
|
+
|
|
147
|
+
常用 Attribute 快速查找:
|
|
148
|
+
- 防重複提交 → IdempotentAttribute
|
|
149
|
+
- 手機/身分證遮罩 → DataMaskAttribute
|
|
150
|
+
- 欄位字典驗證 → DictAttribute
|
|
151
|
+
- 路由至主DB → SysTableAttribute
|
|
152
|
+
- 有種子資料 → SeedDataAttribute
|
|
153
|
+
- 增量更新表結構 → IncreTableAttribute
|