dotnet-testing-unit-test-fundamentals
npx skills add https://github.com/kevintsengtw/dotnet-testing-agent-skills --skill dotnet-testing-unit-test-fundamentals
Agent 安装分布
Skill 文档
.NET å®å 測試åºç¤æå
é©ç¨æ å¢
ç¶è¢«è¦æ±å·è¡ä»¥ä¸ä»»åæï¼è«ä½¿ç¨æ¤æè½ï¼
- çº .NET é¡å¥ææ¹æ³å»ºç«å®å 測試
- æª¢è¦ææ¹é²ç¾ææ¸¬è©¦çå質
- è¨è¨ç¬¦å FIRST ååçæ¸¬è©¦æ¡ä¾
- è§£éæ¸¬è©¦å½åè¦ç¯èæä½³å¯¦è¸
- ä½¿ç¨ xUnit æ°å¯«æ¸¬è©¦
FIRST åå
æ¯åå®å 測試é½å¿ é 符å以ä¸ååï¼
F – Fast (å¿«é)
測試å·è¡æéæå¨æ¯«ç§ç´ï¼ä¸ä¾è³´å¤é¨è³æºã
[Fact] // Fast: ä¸ä¾è³´å¤é¨è³æºï¼å·è¡å¿«é
public void Add_輸å
¥1å2_æåå³3()
{
// ç´è¨æ¶é«éç®ï¼ç¡ I/O æç¶²è·¯å»¶é²
var calculator = new Calculator();
var result = calculator.Add(1, 2);
Assert.Equal(3, result);
}
I – Independent (ç¨ç«)
測試ä¹é䏿æç¸ä¾æ§ï¼æ¯å測試é½å»ºç«æ°ç實ä¾ã
[Fact] // Independent: æ¯å測試é½å»ºç«æ°ç實ä¾
public void Increment_å¾0éå§_æåå³1()
{
var counter = new Counter(); // æ¯å測試é½å»ºç«æ°ç實ä¾ï¼ä¸åå
¶ä»æ¸¬è©¦å½±é¿
counter.Increment();
Assert.Equal(1, counter.Value);
}
R – Repeatable (å¯éè¤)
å¨ä»»ä½ç°å¢é½è½å¾å°ç¸åçµæï¼ä¸ä¾è³´å¤é¨çæ ã
[Fact] // Repeatable: æ¯æ¬¡å·è¡é½å¾å°ç¸åçµæ
public void Increment_夿¬¡å·è¡_æç¢çä¸è´çµæ()
{
var counter = new Counter();
counter.Increment();
counter.Increment();
counter.Increment();
// æ¯æ¬¡å·è¡é忏¬è©¦é½æå¾å°ç¸åçµæ
Assert.Equal(3, counter.Value);
}
S – Self-Validating (èªæé©è)
æ¸¬è©¦çµææçºæç¢ºçééæå¤±æï¼ä½¿ç¨æ¸ æ°çæ·è¨ã
[Fact] // Self-Validating: æç¢ºçé©è
public void IsValidEmail_輸å
¥ææEmail_æåå³True()
{
var emailHelper = new EmailHelper();
var result = emailHelper.IsValidEmail("test@example.com");
Assert.True(result); // æç¢ºçééæå¤±æ
}
T – Timely (åæ)
測試æå¨ç¢åç¨å¼ç¢¼ä¹åæåææ°å¯«ï¼ç¢ºä¿ç¨å¼ç¢¼ç坿¸¬è©¦æ§ã
3A Pattern çµæ§
æ¯åæ¸¬è©¦æ¹æ³å¿ é éµå¾ª Arrange-Act-Assert 模å¼ï¼
[Fact]
public void Add_輸å
¥è² æ¸åæ£æ¸_æå峿£ç¢ºçµæ()
{
// Arrange - æºåæ¸¬è©¦è³æèç¸ä¾ç©ä»¶
var calculator = new Calculator();
const int a = -5;
const int b = 3;
const int expected = -2;
// Act - å·è¡è¢«æ¸¬è©¦çæ¹æ³
var result = calculator.Add(a, b);
// Assert - é©èçµææ¯å¦ç¬¦åé æ
Assert.Equal(expected, result);
}
ååå¡è·è²¬
| åå¡ | è·è²¬ | 注æäºé |
|---|---|---|
| Arrange | æºå測試æéçç©ä»¶ãè³æãMock | ä½¿ç¨ const 宣å常æ¸å¼ï¼æé«å¯è®æ§ |
| Act | å·è¡è¢«æ¸¬è©¦çæ¹æ³ | éå¸¸åªæä¸è¡ï¼å¼å«è¢«æ¸¬æ¹æ³ |
| Assert | é©èçµæ | æ¯å測試åªé©èä¸åè¡çº |
測試å½åè¦ç¯
使ç¨ä»¥ä¸æ ¼å¼å½åæ¸¬è©¦æ¹æ³ï¼
[è¢«æ¸¬è©¦æ¹æ³å稱]_[測試æ
å¢]_[é æè¡çº]
å½åç¯ä¾
| æ¹æ³å稱 | 說æ |
|---|---|
Add_輸å
¥1å2_æåå³3 |
測試æ£å¸¸è¼¸å ¥ |
Add_輸å
¥è² æ¸åæ£æ¸_æå峿£ç¢ºçµæ |
測試éçæ¢ä»¶ |
Divide_輸å
¥10å0_ææåºDivideByZeroException |
測試ä¾å¤æ æ³ |
IsValidEmail_輸å
¥nullå¼_æåå³False |
æ¸¬è©¦ç¡æè¼¸å ¥ |
GetDomain_輸å
¥ææEmail_æåå³ç¶²åå稱 |
測試åå³å¼ |
ð¡ æç¤ºï¼ä½¿ç¨ä¸æå½åå¯ä»¥è®æ¸¬è©¦å ±åæ´æè®ï¼ç¹å¥æ¯å¨åéæºéæã
xUnit 測試屬æ§
[Fact] – å®ä¸æ¸¬è©¦æ¡ä¾
ç¨æ¼æ¸¬è©¦å®ä¸æ å¢ï¼
[Fact]
public void Add_輸å
¥0å0_æåå³0()
{
var calculator = new Calculator();
var result = calculator.Add(0, 0);
Assert.Equal(0, result);
}
[Theory] + [InlineData] – 忏忏¬è©¦
ç¨æ¼æ¸¬è©¦å¤åè¼¸å ¥çµåï¼
[Theory]
[InlineData(1, 2, 3)]
[InlineData(-1, 1, 0)]
[InlineData(0, 0, 0)]
[InlineData(100, -50, 50)]
public void Add_輸å
¥å種æ¸å¼çµå_æå峿£ç¢ºçµæ(int a, int b, int expected)
{
var calculator = new Calculator();
var result = calculator.Add(a, b);
Assert.Equal(expected, result);
}
測試å¤åç¡æè¼¸å ¥
[Theory]
[InlineData("invalid-email")]
[InlineData("@example.com")]
[InlineData("test@")]
[InlineData("test.example.com")]
public void IsValidEmail_輸å
¥ç¡æEmailæ ¼å¼_æåå³False(string invalidEmail)
{
var emailHelper = new EmailHelper();
var result = emailHelper.IsValidEmail(invalidEmail);
Assert.False(result);
}
ä¾å¤æ¸¬è©¦
æ¸¬è©¦é æææåºä¾å¤çæ æ³ï¼
[Fact]
public void Divide_輸å
¥10å0_ææåºDivideByZeroException()
{
// Arrange
var calculator = new Calculator();
const decimal dividend = 10m;
const decimal divisor = 0m;
// Act & Assert
var exception = Assert.Throws<DivideByZeroException>(
() => calculator.Divide(dividend, divisor)
);
// é©èä¾å¤è¨æ¯
Assert.Equal("餿¸ä¸è½çºé¶", exception.Message);
}
æ¸¬è©¦å°æ¡çµæ§
建è°çå°æ¡çµæ§ï¼
Solution/
âââ src/
â âââ MyProject/
â âââ Calculator.cs
â âââ MyProject.csproj
âââ tests/
âââ MyProject.Tests/
âââ CalculatorTests.cs
âââ MyProject.Tests.csproj
æ¸¬è©¦å°æ¡ç¯æ¬ (.csproj)
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\MyProject\MyProject.csproj" />
</ItemGroup>
</Project>
å¸¸ç¨æ·è¨æ¹æ³
| æ·è¨æ¹æ³ | ç¨é |
|---|---|
Assert.Equal(expected, actual) |
é©èç¸ç |
Assert.NotEqual(expected, actual) |
é©èä¸ç¸ç |
Assert.True(condition) |
é©èæ¢ä»¶çºç |
Assert.False(condition) |
é©èæ¢ä»¶çºå |
Assert.Null(object) |
é©èçº null |
Assert.NotNull(object) |
é©èä¸çº null |
Assert.Throws<T>(action) |
é©èæåºç¹å®ä¾å¤ |
Assert.Empty(collection) |
é©èéåçºç©º |
Assert.Contains(item, collection) |
é©èéåå å«é ç® |
çææ¸¬è©¦çæª¢æ¥æ¸ å®
çºæ¹æ³çææ¸¬è©¦æï¼è«ç¢ºä¿æ¶µèï¼
- æ£å¸¸è·¯å¾ – æ¨æºè¼¸å ¥ç¢çé æè¼¸åº
- éçæ¢ä»¶ – æå°å¼ãæå¤§å¼ãé¶ã空å串
- ç¡æè¼¸å ¥ – nullãè² æ¸ãæ ¼å¼é¯èª¤
- ä¾å¤æ æ³ – é æææåºä¾å¤çæ å¢
åèè³æº
åå§æç«
æ¬æè½å §å®¹æç èªãèæ´¾è»é«å·¥ç¨å¸«ç測試修練 – 30 å¤©ææ°ãç³»åæç« ï¼
- Day 01 – èæ´¾å·¥ç¨å¸«ç測試åè
- éµäººè³½æç« ï¼https://ithelp.ithome.com.tw/articles/10373888
- ç¯ä¾ç¨å¼ç¢¼ï¼https://github.com/kevintsengtw/30Days_in_Testing_Samples/tree/main/day01