

Also from Kynth Studios


Also from Kynth Studios


Also from Kynth Studios
12345678910# 测试开发规范1112## 测试项目结构1314```15Tests/16├── ApiServices/ # API服务测试17│ ├── CodeSpirit.ExamApi.Tests/18│ └── CodeSpirit.IdentityApi.Tests/19├── Components/ # 组件测试20│ ├── CodeSpirit.Amis.Tests/21│ └── CodeSpirit.LLM.Tests/22├── Infrastructure/ # 基础设施测试23└── Shared/ # 共享测试工具24```2526## 测试命名约定2728### 测试类命名29- 格式:`{被测试类名}Tests`30- 示例:`UserServiceTests`、`QuestionsControllerTests`3132### 测试方法命名33- 格式:`{方法名}_{场景}_{预期结果}`34- 使用 `[Fact]` 或 `[Theory]` 特性35- 示例:36```csharp37 [Fact]38 public async Task GetByIdAsync_ValidId_ReturnsUserDto()39 {40 // Arrange41 // Act42 // Assert43 }4445 [Theory]46 [InlineData(1)]47 [InlineData(2)]48 public async Task GetByIdAsync_InvalidId_ThrowsNotFoundException(long id)49 {50 // Arrange51 // Act & Assert52 }53```5455## 测试框架5657- **单元测试**: xUnit58- **Mock框架**: Moq 或 NSubstitute59- **断言**: FluentAssertions(推荐)或 xUnit 内置断言6061## 单元测试示例6263```csharp64using Xunit;65using FluentAssertions;66using Moq;67using CodeSpirit.ExamApi.Services;68using CodeSpirit.ExamApi.Services.Interfaces;6970namespace CodeSpirit.ExamApi.Tests.Services;7172/// <summary>73/// 题目服务测试74/// </summary>75public class QuestionServiceTests76{77 private readonly Mock<IRepository<Question>> _repositoryMock;78 private readonly Mock<IMapper> _mapperMock;79 private readonly QuestionService _service;8081 public QuestionServiceTests()82 {83 _repositoryMock = new Mock<IRepository<Question>>();84 _mapperMock = new Mock<IMapper>();85 _service = new QuestionService(_repositoryMock.Object, _mapperMock.Object);86 }8788 [Fact]89 public async Task GetQuestionAsync_ValidId_ReturnsQuestionDto()90 {91 // Arrange92 var questionId = 1L;93 var question = new Question { Id = questionId, Content = "测试题目" };94 var questionDto = new QuestionDto { Id = questionId, Content = "测试题目" };9596 _repositoryMock.Setup(r => r.GetByIdAsync(questionId))97 .ReturnsAsync(question);98 _mapperMock.Setup(m => m.Map<QuestionDto>(question))99 .Returns(questionDto);100101 // Act102 var result = await _service.GetQuestionAsync(questionId);103104 // Assert105 result.Should().NotBeNull();106 result.Id.Should().Be(questionId);107 result.Content.Should().Be("测试题目");108 _repositoryMock.Verify(r => r.GetByIdAsync(questionId), Times.Once);109 }110111 [Fact]112 public async Task GetQuestionAsync_InvalidId_ThrowsNotFoundException()113 {114 // Arrange115 var questionId = 999L;116 _repositoryMock.Setup(r => r.GetByIdAsync(questionId))117 .ReturnsAsync((Question?)null);118119 // Act & Assert120 await Assert.ThrowsAsync<BusinessException>(() =>121 _service.GetQuestionAsync(questionId));122 }123}124```125126## 集成测试示例127128```csharp129using Xunit;130using Microsoft.AspNetCore.Mvc.Testing;131using CodeSpirit.ExamApi;132using System.Net.Http;133using System.Threading.Tasks;134135namespace CodeSpirit.ExamApi.Tests.Integration;136137/// <summary>138/// 题目API集成测试139/// </summary>140public class QuestionsControllerIntegrationTests : IClassFixture<WebApplicationFactory<Program>>141{142 private readonly HttpClient _client;143144 public QuestionsControllerIntegrationTests(WebApplicationFactory<Program> factory)145 {146 _client = factory.CreateClient();147 }148149 [Fact]150 public async Task GetQuestions_ReturnsSuccessStatusCode()151 {152 // Arrange153 var request = new HttpRequestMessage(HttpMethod.Get, "/exam/api/Questions");154155 // Act156 var response = await _client.SendAsync(request);157158 // Assert159 response.EnsureSuccessStatusCode();160 var content = await response.Content.ReadAsStringAsync();161 content.Should().NotBeNullOrEmpty();162 }163}164```165166## Mock 使用规范167168### 使用 Moq169170```csharp171// 设置返回值172_mockService.Setup(s => s.GetByIdAsync(It.IsAny<long>()))173 .ReturnsAsync(new UserDto { Id = 1 });174175// 验证调用176_mockService.Verify(s => s.GetByIdAsync(1), Times.Once);177178// 设置异常179_mockService.Setup(s => s.GetByIdAsync(It.IsAny<long>()))180 .ThrowsAsync(new BusinessException("用户不存在"));181```182183### 使用 NSubstitute184185```csharp186// 设置返回值187_substituteService.GetByIdAsync(Arg.Any<long>())188 .Returns(new UserDto { Id = 1 });189190// 验证调用191_substituteService.Received(1).GetByIdAsync(1);192193// 设置异常194_substituteService.GetByIdAsync(Arg.Any<long>())195 .ThrowsAsync(new BusinessException("用户不存在"));196```197198## 测试数据准备199200### 使用 Fixture201202```csharp203public class QuestionFixture204{205 public static Question CreateValidQuestion()206 {207 return new Question208 {209 Id = 1,210 Content = "测试题目",211 Type = QuestionType.SingleChoice,212 CreatedAt = DateTime.UtcNow213 };214 }215216 public static CreateQuestionDto CreateValidCreateDto()217 {218 return new CreateQuestionDto219 {220 Content = "测试题目",221 Type = QuestionType.SingleChoice222 };223 }224}225```226227## 异步测试228229```csharp230[Fact]231public async Task CreateQuestionAsync_ValidDto_ReturnsQuestionDto()232{233 // Arrange234 var dto = QuestionFixture.CreateValidCreateDto();235236 // Act237 var result = await _service.CreateQuestionAsync(dto);238239 // Assert240 result.Should().NotBeNull();241 result.Content.Should().Be(dto.Content);242}243```244245## 测试覆盖率要求246247- **核心业务逻辑**: 覆盖率 ≥ 80%248- **服务层**: 覆盖率 ≥ 70%249- **控制器**: 覆盖率 ≥ 60%(重点测试业务逻辑,不测试框架功能)250251## 注意事项252253- ✅ 每个测试方法应该独立,不依赖其他测试的执行顺序254- ✅ 使用 `[Fact]` 进行独立测试,使用 `[Theory]` 进行参数化测试255- ✅ 测试方法应遵循 AAA 模式(Arrange-Act-Assert)256- ✅ Mock 对象应在测试类构造函数或 `[SetUp]` 方法中初始化257- ✅ 使用有意义的测试数据,避免魔法数字和字符串258- ✅ 测试异常场景,确保错误处理正确259- ❌ 不要测试框架功能(如 EF Core、AutoMapper 等)260- ❌ 不要编写过于复杂的测试,保持测试简单清晰261262## 参考文档263264- xUnit 文档: https://xunit.net/265- Moq 文档: https://github.com/moq/moq4266- FluentAssertions 文档: https://fluentassertions.com/267
One repository carrying more than one format is the comparison this product exists for: does anyone actually write different content in each file, or is one a copy of the other?
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| xin-lai/CodeSpirit.cursor/rules/api-design.mdc · 56 | Cursor rules | no sections | 54/100 | 14 days ago | |
| xin-lai/CodeSpirit.cursor/rules/controller.mdc · 56 | Cursor rules | api | 54/100 | 14 days ago | |
| xin-lai/CodeSpirit.cursor/rules/dependency-injection.mdc · 56 | Cursor rules | api | 54/100 | 14 days ago | |
| xin-lai/CodeSpirit.cursor/rules/dto.mdc · 56 | Cursor rules | no sections | 50/100 | 14 days ago | |
| xin-lai/CodeSpirit.cursor/rules/js.mdc · 56 | Cursor rules | apidocs | 46/100 | 14 days ago | |
| xin-lai/CodeSpirit.cursor/rules/security.mdc · 56 | Cursor rules | database | 54/100 | 14 days ago | |
| xin-lai/CodeSpirit.cursor/rules/service.mdc · 56 | Cursor rules | no sections | 50/100 | 14 days ago | |
| xin-lai/CodeSpiritAGENTS.md · 56 | AGENTS.md | styleagent-behaviourdocs | 66/100 | 14 days ago | |
| xin-lai/CodeSpirit.cursor/rules/ai-development.mdc · 56 | Cursor rules | api | 46/100 | 14 days ago | |
| xin-lai/CodeSpirit.cursor/rules/amis-cards.mdc · 56 | Cursor rules | no sections | 54/100 | 14 days ago | |
| xin-lai/CodeSpirit.cursor/rules/csproj.mdc · 56 | Cursor rules | api | 54/100 | 14 days ago | |
| xin-lai/CodeSpirit.cursor/rules/database.mdc · 56 | Cursor rules | no sections | 74/100 | 14 days ago | |
| xin-lai/CodeSpirit.cursor/rules/naming-conventions.mdc · 56 | Cursor rules | no sections | 50/100 | 14 days ago | |
| xin-lai/CodeSpirit.cursor/rules/package-management.mdc · 56 | Cursor rules | no sections | 74/100 | 14 days ago | |
| xin-lai/CodeSpirit.cursor/rules/project-structure.mdc · 56 | Cursor rules | api | 54/100 | 14 days ago | |
| xin-lai/CodeSpirit.cursor/rules/cs.mdc · 56 | Cursor rules | no sections | 25/100 | 14 days ago | |
| xin-lai/CodeSpirit.cursor/rules/all.mdc · 56 | Cursor rules | testing-strategyapi | 50/100 | 14 days ago | |
| xin-lai/CodeSpirit.cursor/rules/css.mdc · 56 | Cursor rules | ui | 54/100 | 14 days ago | |
| xin-lai/CodeSpirit.cursor/rules/enum.mdc · 56 | Cursor rules | no sections | 50/100 | 14 days ago | |
| xin-lai/CodeSpirit.cursor/rules/i18n.mdc · 56 | Cursor rules | no sections | 50/100 | 14 days ago |
Same format, overlapping stack, ranked by quality.
| Repository | Format | Stack | Covers | Score | Changed |
|---|---|---|---|---|---|
| hiromaily/go-crypto-wallet.cursor/rules/typescript.mdc · 126 | Cursor rules | setupbuildtestlint-format+6 | 100/100 | 14 days ago | |
| TechSquidTV/Hermes.cursor/rules/10-hermes-api.mdc · 46 | Cursor rules | testlint-formatstylearch+5 | 100/100 | 14 days ago | |
| markstev/mark-starter.cursor/rules/frontend.mdc · 0 | Cursor rules | setuptestlint-formatstyle+6 | 99/100 | 14 days ago | |
| deifos/clipmira-subtitles.cursor/rules/frontend.mdc · 1 | Cursor rules | setuptestlint-formatstyle+7 | 99/100 | 14 days ago | |
| dodgecfr/combatfilms-webapp.cursor/rules/frontend.mdc · 0 | Cursor rules | setuptestlint-formatstyle+7 | 99/100 | 14 days ago | |
| Allymahmoud/case-intake-platform.cursor/rules/frontend.mdc · 0 | Cursor rules | setuptestlint-formatstyle+7 | 99/100 | 14 days ago | |
| langflow-ai/langflow.cursor/rules/docs_development.mdc · 153k | Cursor rules | setupbuildtestlint-format+7 | 97/100 | 14 days ago | |
| TechSquidTV/Hermes.cursor/rules/20-hermes-api-tests.mdc · 46 | Cursor rules | teststyletesting-strategysecurity+3 | 97/100 | 14 days ago |
A badge carrying the measured quality of the strongest agent config file in this repository, out of 100. It reads from this index every time somebody loads your page, so it changes when the measurement changes and there is nothing to keep up to date. Free, no account, and the value is not something you or we can set by hand.
[](https://rulestack.kynth.studio/configs/xin-lai-codespirit-cursor-rules-testing)Would rather not hotlink us? Every badge is also served in shields.io’s endpoint schema, so shields renders the image and your readers never talk to our domain:
Published by Toolproof, the masthead over this index and eight others. The method behind the number is at toolproof.kynth.studio/methodology, and the whole thing is readable as JSON with no key at /api.