dotnet-testing-fluentvalidation-testing
16
总安装量
14
周安装量
#21294
全站排名
安装命令
npx skills add https://github.com/kevintsengtw/dotnet-testing-agent-skills --skill dotnet-testing-fluentvalidation-testing
Agent 安装分布
gemini-cli
10
claude-code
10
antigravity
9
opencode
8
windsurf
6
github-copilot
6
Skill 文档
FluentValidation é©è卿¸¬è©¦æå
é©ç¨æ å¢
æ¤æè½å°æ³¨æ¼ä½¿ç¨ FluentValidation.TestHelper æ¸¬è©¦è³æé©èéè¼¯ï¼æ¶µèåºæ¬é©èãè¤éæ¥åè¦åãé忥é©è忏¬è©¦æä½³å¯¦è¸ã
çºä»éº¼è¦æ¸¬è©¦é©èå¨ï¼
é©è卿¯æç¨ç¨å¼ç第ä¸éé²ç·ï¼æ¸¬è©¦é©èå¨è½ï¼
- 確ä¿è³æå®æ´æ§ – 鲿¢ç¡æè³æé²å ¥ç³»çµ±
- æ¥åè¦åæä»¶å – æ¸¬è©¦å³æ´»æä»¶ï¼æ¸ æ¥å±ç¤ºæ¥åè¦å
- å®å ¨æ§ä¿é – 鲿¢æ¡ææä¸ç¶è³æè¼¸å ¥
- éæ§å®å ¨ç¶² – æ¥åè¦åè®æ´ææä¾ä¿é
- è·¨æ¬ä½é輯é©è – 確ä¿è¤éé輯æ£ç¢ºéä½
åç½®éæ±
å¥ä»¶å®è£
<PackageReference Include="FluentValidation" Version="11.11.0" />
<PackageReference Include="FluentValidation.TestHelper" Version="11.11.0" />
<PackageReference Include="xunit" Version="2.9.3" />
<PackageReference Include="Microsoft.Extensions.Time.Testing" Version="9.0.0" />
<PackageReference Include="NSubstitute" Version="5.3.0" />
<PackageReference Include="AwesomeAssertions" Version="9.1.0" />
åºæ¬ using æä»¤
using FluentValidation;
using FluentValidation.TestHelper;
using Microsoft.Extensions.Time.Testing;
using NSubstitute;
using Xunit;
using AwesomeAssertions;
æ ¸å¿æ¸¬è©¦æ¨¡å¼
æ¬ç¯æ¶µè 7 ç¨®æ ¸å¿æ¸¬è©¦æ¨¡å¼ï¼æ¯ç¨®æ¨¡å¼å å«é©èå¨å®ç¾©è宿´æ¸¬è©¦ç¯ä¾ã
ð 宿´ç¨å¼ç¢¼ç¯ä¾è«åè references/core-test-patterns.md
- æ¨¡å¼ 1ï¼åºæ¬æ¬ä½é©è â 使ç¨
TestValidate+ShouldHaveValidationErrorFor/ShouldNotHaveValidationErrorFor測試å®ä¸æ¬ä½è¦å - æ¨¡å¼ 2ï¼åæ¸å測試 â 使ç¨
[Theory]+[InlineData]測試å¤ç¨®ç¡æ/ææè¼¸å ¥çµå - æ¨¡å¼ 3ï¼è·¨æ¬ä½é©è â å¯ç¢¼ç¢ºèªãèªè¨
Must()è¦åç夿¬ä½éè¯é©è - æ¨¡å¼ 4ï¼æéç¸ä¾é©è â æ³¨å
¥
TimeProviderï¼æé FakeTimeProvideræ§å¶æéé²è¡æ¸¬è©¦ - æ¨¡å¼ 5ï¼æ¢ä»¶å¼é©è â 使ç¨
.When()çå¯é¸æ¬ä½é©èï¼æ¸¬è©¦æ¢ä»¶è§¸ç¼èè·³éæ å¢ - æ¨¡å¼ 6ï¼é忥é©è â
MustAsync+TestValidateAsyncï¼æé NSubstitute Mock å¤é¨æå - æ¨¡å¼ 7ï¼éåé©è â é©èéåé空èå ç´ æææ§
å¿«éç¯ä¾ï¼åºæ¬æ¬ä½é©è
public class UserValidatorTests
{
private readonly UserValidator _validator = new();
[Fact]
public void Validate_空ç½ä½¿ç¨è
å稱_æè©²é©è失æ()
{
var result = _validator.TestValidate(
new UserRegistrationRequest { Username = "" });
result.ShouldHaveValidationErrorFor(x => x.Username)
.WithErrorMessage("使ç¨è
å稱ä¸å¯çº null æç©ºç½");
}
}
FluentValidation.TestHelper æ ¸å¿ API
æ¸¬è©¦æ¹æ³
| æ¹æ³ | ç¨é | ç¯ä¾ |
|---|---|---|
TestValidate(model) |
å·è¡åæ¥é©è | _validator.TestValidate(request) |
TestValidateAsync(model) |
å·è¡é忥é©è | await _validator.TestValidateAsync(request) |
æ·è¨æ¹æ³
| æ¹æ³ | ç¨é | ç¯ä¾ |
|---|---|---|
ShouldHaveValidationErrorFor(x => x.Property) |
æ·è¨è©²å±¬æ§æè©²æé¯èª¤ | result.ShouldHaveValidationErrorFor(x => x.Username) |
ShouldNotHaveValidationErrorFor(x => x.Property) |
æ·è¨è©²å±¬æ§ä¸æè©²æé¯èª¤ | result.ShouldNotHaveValidationErrorFor(x => x.Email) |
ShouldNotHaveAnyValidationErrors() |
æ·è¨æ´åç©ä»¶æ²æä»»ä½é¯èª¤ | result.ShouldNotHaveAnyValidationErrors() |
é¯èª¤è¨æ¯é©è
| æ¹æ³ | ç¨é | ç¯ä¾ |
|---|---|---|
WithErrorMessage(string) |
é©èé¯èª¤è¨æ¯å §å®¹ | .WithErrorMessage("使ç¨è
å稱ä¸å¯çºç©º") |
WithErrorCode(string) |
é©èé¯èª¤ä»£ç¢¼ | .WithErrorCode("NOT_EMPTY") |
測試æä½³å¯¦è¸
â æ¨è¦åæ³
- 使ç¨åæ¸å測試 – ç¨ Theory 測試å¤ç¨®è¼¸å ¥çµå
- 測試éçå¼ – ç¹å¥æ³¨æéçæ¢ä»¶
- æ§å¶æé – ä½¿ç¨ FakeTimeProvider èçæéç¸ä¾
- Mock å¤é¨ä¾è³´ – ä½¿ç¨ NSubstitute éé¢å¤é¨æå
- 建ç«è¼å©æ¹æ³ – çµ±ä¸ç®¡çæ¸¬è©¦è³æ
- æ¸
æ¥ç測試å½å – 使ç¨
æ¹æ³_æ å¢_é æçµææ ¼å¼ - 測試é¯èª¤è¨æ¯ – 確ä¿ä½¿ç¨è çå°æ£ç¢ºçé¯èª¤è¨æ¯
â é¿å åæ³
- é¿å ä½¿ç¨ DateTime.Now – æå°è´æ¸¬è©¦ä¸ç©©å®
- é¿å 測試é度è¦å – æ¯å測試åªé©èä¸åè¦å
- é¿å ç¡¬ç·¨ç¢¼æ¸¬è©¦è³æ – 使ç¨è¼å©æ¹æ³å»ºç«
- é¿å 忽ç¥éçæ¢ä»¶ – éç弿¯æå®¹æåºé¯çå°æ¹
- é¿å è·³éé¯èª¤è¨æ¯é©è – é¯èª¤è¨æ¯æ¯ä½¿ç¨è é«é©çä¸é¨å
å¸¸è¦æ¸¬è©¦å ´æ¯
å ´æ¯ 1ï¼Email æ ¼å¼é©è
[Theory]
[InlineData("", "é»åéµä»¶ä¸å¯çº null æç©ºç½")]
[InlineData("invalid", "é»åéµä»¶æ ¼å¼ä¸æ£ç¢º")]
[InlineData("@example.com", "é»åéµä»¶æ ¼å¼ä¸æ£ç¢º")]
public void Validate_ç¡æEmail_æè©²é©è失æ(string email, string expectedError)
{
var request = new UserRegistrationRequest { Email = email };
var result = _validator.TestValidate(request);
result.ShouldHaveValidationErrorFor(x => x.Email).WithErrorMessage(expectedError);
}
å ´æ¯ 2ï¼å¹´é½¡ç¯åé©è
[Theory]
[InlineData(17, "年齡å¿
é å¤§æ¼æçæ¼ 18 æ²")]
[InlineData(121, "年齡å¿
é å°æ¼æçæ¼ 120 æ²")]
public void Validate_ç¡æå¹´é½¡_æè©²é©è失æ(int age, string expectedError)
{
var request = new UserRegistrationRequest { Age = age };
var result = _validator.TestValidate(request);
result.ShouldHaveValidationErrorFor(x => x.Age).WithErrorMessage(expectedError);
}
å ´æ¯ 3ï¼å¿ å¡«æ¬ä½é©è
[Fact]
public void Validate_æªåææ¢æ¬¾_æè©²é©è失æ()
{
var request = new UserRegistrationRequest { AgreeToTerms = false };
var result = _validator.TestValidate(request);
result.ShouldHaveValidationErrorFor(x => x.AgreeToTerms)
.WithErrorMessage("å¿
é åæä½¿ç¨æ¢æ¬¾");
}
測試è¼å©å·¥å ·
æ¸¬è©¦è³æå»ºæ§å¨
public static class TestDataBuilder
{
public static UserRegistrationRequest CreateValidRequest()
{
return new UserRegistrationRequest
{
Username = "testuser123",
Email = "test@example.com",
Password = "TestPass123",
ConfirmPassword = "TestPass123",
BirthDate = new DateTime(1990, 1, 1),
Age = 34,
PhoneNumber = "0912345678",
Roles = new List<string> { "User" },
AgreeToTerms = true
};
}
public static UserRegistrationRequest WithUsername(this UserRegistrationRequest request, string username)
{
request.Username = username;
return request;
}
public static UserRegistrationRequest WithEmail(this UserRegistrationRequest request, string email)
{
request.Email = email;
return request;
}
}
// 使ç¨ç¯ä¾
var request = TestDataBuilder.CreateValidRequest()
.WithUsername("newuser")
.WithEmail("new@example.com");
èå ¶ä»æè½æ´å
æ¤æè½å¯è以䏿è½çµå使ç¨ï¼
- unit-test-fundamentals: å®å 測試åºç¤è 3A 模å¼
- test-naming-conventions: 測試å½åè¦ç¯
- nsubstitute-mocking: Mock å¤é¨æåä¾è³´
- test-data-builder-pattern: 建æ§è¤éæ¸¬è©¦è³æ
- datetime-testing-timeprovider: æéç¸ä¾æ¸¬è©¦
çé£æè§£
Q1: å¦ä½æ¸¬è©¦éè¦è³æåº«æ¥è©¢çé©èï¼
A: ä½¿ç¨ Mock éé¢è³æåº«ä¾è³´ï¼
_mockUserService.IsUsernameAvailableAsync("username")
.Returns(Task.FromResult(false));
Q2: å¦ä½èçæéç¸éçé©èï¼
A: ä½¿ç¨ FakeTimeProvider æ§å¶æéï¼
_fakeTimeProvider.SetUtcNow(new DateTime(2024, 1, 1));
Q3: å¦ä½æ¸¬è©¦è¤éçè·¨æ¬ä½é©èï¼
A: å奿¸¬è©¦æ¯åæ¢ä»¶ï¼ç¢ºä¿å®æ´è¦èï¼
// æ¸¬è©¦çæ¥å·²éçæ
æ³
// æ¸¬è©¦çæ¥æªå°çæ
æ³
// 測試éçæ¥æ
Q4: æè©²æ¸¬è©¦å°ä»éº¼ç¨åº¦ï¼
A: é黿¸¬è©¦ï¼
- æ¯åé©èè¦åè³å°ä¸å測試
- éçå¼åç¹æ®æ æ³
- é¯èª¤è¨æ¯æ£ç¢ºæ§
- è·¨æ¬ä½éè¼¯çææçµå
ç¯æ¬æªæ¡åè
æ¬æè½æä¾ä»¥ä¸ç¯æ¬æªæ¡ï¼
templates/validator-test-template.cs: 宿´çé©è卿¸¬è©¦ç¯ä¾templates/async-validator-examples.cs: é忥é©èç¯ä¾
åèè³æº
åå§æç«
æ¬æè½å §å®¹æç èªãèæ´¾è»é«å·¥ç¨å¸«ç測試修練 – 30 å¤©ææ°ãç³»åæç« ï¼
- Day 18 – é©è測試ï¼FluentValidation Test Extensions
- éµäººè³½æç« ï¼https://ithelp.ithome.com.tw/articles/10376147
- ç¯ä¾ç¨å¼ç¢¼ï¼https://github.com/kevintsengtw/30Days_in_Testing_Samples/tree/main/day18
宿¹æä»¶
ç¸éæè½
unit-test-fundamentals– å®å 測試åºç¤nsubstitute-mocking– 測試æ¿èº«è模æ¬