Blog.Core 单元测试:Repository 与 Service 层测试实践

Blog.Core 单元测试:Repository 与 Service 层测试实践

【免费下载链接】Blog.Core 💖 ASP.NET Core 8.0 全家桶教程,前后端分离后端接口,vue教程姊妹篇,官方文档: 【免费下载链接】Blog.Core 项目地址: https://gitcode.com/gh_mirrors/bl/Blog.Core

引言:为什么单元测试对 Blog.Core 至关重要

在 ASP.NET Core 项目开发中,单元测试是保障代码质量和系统稳定性的关键环节。对于 Blog.Core 这样的企业级后端框架,Repository 层(数据访问层)和 Service 层(业务逻辑层)的健壮性直接决定了整个应用的可靠性。然而,许多开发者在实际开发中常面临以下痛点:

  • 数据依赖困境:测试需连接真实数据库,导致测试速度慢且不稳定
  • 业务逻辑黑盒:复杂的业务规则难以通过手动测试全面覆盖
  • 重构风险:修改代码时缺乏自动化验证机制,容易引入回归 bug

本文将系统讲解如何为 Blog.Core 项目构建完善的单元测试体系,重点解决上述问题,让你能够:

  • 使用 Mock 技术隔离外部依赖,实现"无数据库"测试
  • 设计高覆盖率的测试用例,验证 Repository 层核心方法
  • 构建 Service 层测试策略,确保业务逻辑正确性
  • 掌握测试驱动开发(TDD)在实际项目中的应用技巧

测试环境搭建与核心依赖

测试项目结构解析

Blog.Core 项目已内置测试项目 Blog.Core.Tests,其结构如下:

Blog.Core.Tests/
├── Common_Test/        // 通用测试工具类
├── Controller_Test/    // 控制器测试
├── DependencyInjection/ // 测试依赖注入配置
├── Redis_Test/         // Redis缓存测试
├── Repository_Test/    // 数据访问层测试
├── Service_Test/       // 业务逻辑层测试
└── appsettings.json    // 测试配置文件

核心测试依赖

Blog.Core.Tests.csproj 中需要添加以下 NuGet 包:

<PackageReference Include="xunit" Version="2.5.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
<PackageReference Include="Moq" Version="4.20.70" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="8.0.0" />
<PackageReference Include="coverlet.collector" Version="6.0.0" />
  • xunit:.NET 平台主流测试框架
  • Moq:强大的模拟对象库,用于隔离外部依赖
  • EF Core InMemory:内存数据库,用于 Repository 层集成测试
  • coverlet:代码覆盖率收集工具

Repository 层测试实战

Repository 层架构回顾

Blog.Core 的 Repository 层基于 SqlSugar ORM 实现,核心抽象为 IBaseRepository<TEntity>,具体实现类为 BaseRepository<TEntity>。其主要功能包括:

public interface IBaseRepository<TEntity> where TEntity : class, new()
{
    Task<TEntity> QueryById(object objId);
    Task<long> Add(TEntity entity);
    Task<bool> Update(TEntity entity);
    Task<bool> DeleteById(object id);
    Task<List<TEntity>> Query(Expression<Func<TEntity, bool>> whereExpression);
    Task<PageModel<TEntity>> QueryPage(Expression<Func<TEntity, bool>> whereExpression, 
                                      int pageIndex = 1, int pageSize = 20);
}

单元测试策略设计

Repository 层测试面临的核心挑战是如何隔离数据库依赖。我们采用"分层测试策略":

  1. 单元测试:使用 Moq 模拟 ISqlSugarClient,测试 Repository 层逻辑
  2. 集成测试:使用 EF Core InMemory 数据库,测试数据访问逻辑
  3. 组件测试:针对分表、事务等复杂场景的端到端测试

基础 Repository 测试实现

BaseRepository<TEntity>.Add 方法测试为例:

public class BaseRepositoryTests
{
    private readonly Mock<ISqlSugarClient> _sqlSugarClientMock;
    private readonly IBaseRepository<BlogArticle> _blogArticleRepository;

    public BaseRepositoryTests()
    {
        // 1. 创建 SqlSugarClient 模拟对象
        _sqlSugarClientMock = new Mock<ISqlSugarClient>();
        
        // 2. 配置 Insertable 方法模拟行为
        var insertableMock = new Mock<ISqlSugarInsertable<BlogArticle>>();
        insertableMock.Setup(m => m.ExecuteReturnSnowflakeIdAsync())
                      .ReturnsAsync(1423567890123456789);
                      
        _sqlSugarClientMock.Setup(m => m.Insertable(It.IsAny<BlogArticle>()))
                          .Returns(insertableMock.Object);

        // 3. 初始化测试目标对象
        _blogArticleRepository = new BaseRepository<BlogArticle>(
            Mock.Of<IUnitOfWorkManage>(uow => 
                uow.GetDbClient() == _sqlSugarClientMock.Object)
        );
    }

    [Fact]
    public async Task Add_ValidEntity_ReturnsSnowflakeId()
    {
        // Arrange
        var testArticle = new BlogArticle
        {
            Title = "测试文章",
            Content = "这是一篇用于测试的文章",
            CreateTime = DateTime.Now
        };

        // Act
        var resultId = await _blogArticleRepository.Add(testArticle);

        // Assert
        Assert.True(resultId > 0);
        Assert.Equal(1423567890123456789, resultId);
        
        // 验证 Insertable 方法是否被正确调用
        _sqlSugarClientMock.Verify(
            m => m.Insertable(It.Is<BlogArticle>(a => a.Title == "测试文章")),
            Times.Once
        );
    }
}

分页查询测试:复杂场景处理

分页查询是 Repository 层的核心功能,需要测试多种边界情况:

[Theory]
[InlineData(1, 10, 25, 3)]  // 第1页,10条/页,共25条,应返回10条
[InlineData(3, 10, 25, 5)]   // 第3页,10条/页,共25条,应返回5条
[InlineData(4, 10, 25, 0)]   // 第4页,10条/页,共25条,应返回0条
public async Task QueryPage_WithDifferentParameters_ReturnsCorrectData(
    int pageIndex, int pageSize, int totalItems, int expectedItems)
{
    // Arrange
    var mockDb = new Mock<ISqlSugarClient>();
    var mockQueryable = new Mock<ISugarQueryable<BlogArticle>>();
    
    // 设置分页查询模拟行为
    var totalCount = new RefAsync<int>(totalItems);
    var mockList = Enumerable.Range(0, expectedItems)
        .Select(i => new BlogArticle { Id = i })
        .ToList();
        
    mockQueryable.Setup(m => m.ToPageListAsync(
        pageIndex, pageSize, totalCount, default))
        .ReturnsAsync(mockList);
        
    mockDb.Setup(m => m.Queryable<BlogArticle>())
          .Returns(mockQueryable.Object);

    var repository = new BaseRepository<BlogArticle>(
        Mock.Of<IUnitOfWorkManage>(uow => uow.GetDbClient() == mockDb.Object)
    );

    // Act
    var result = await repository.QueryPage(
        a => a.Id > 0, pageIndex, pageSize);

    // Assert
    Assert.NotNull(result);
    Assert.Equal(pageIndex, result.pageIndex);
    Assert.Equal(totalItems, result.totalCount);
    Assert.Equal(pageSize, result.pageSize);
    Assert.Equal(expectedItems, result.data.Count);
}

集成测试:使用内存数据库

对于需要验证 SQL 生成逻辑的场景,可使用 EF Core InMemory 数据库:

public class BlogArticleRepositoryIntegrationTests : IDisposable
{
    private readonly SqlSugarScope _inMemoryDb;
    private readonly IBaseRepository<BlogArticle> _repository;

    public BlogArticleRepositoryIntegrationTests()
    {
        // 初始化内存数据库
        _inMemoryDb = new SqlSugarScope(new ConnectionConfig
        {
            DbType = DbType.Sqlite,
            ConnectionString = "DataSource=:memory:",
            IsAutoCloseConnection = true
        });
        
        // 创建表结构
        _inMemoryDb.CodeFirst.InitTables<BlogArticle>();
        
        // 初始化仓储
        _repository = new BaseRepository<BlogArticle>(
            new UnitOfWorkManage(_inMemoryDb)
        );
    }

    [Fact]
    public async Task Query_WithFilter_ReturnsFilteredResults()
    {
        // Arrange: 准备测试数据
        await _repository.Add(new BlogArticle { Title = "ASP.NET Core教程", Category = "技术" });
        await _repository.Add(new BlogArticle { Title = "C#高级特性", Category = "技术" });
        await _repository.Add(new BlogArticle { Title = "人生感悟", Category = "随笔" });

        // Act: 执行查询
        var result = await _repository.Query(a => a.Category == "技术");

        // Assert: 验证结果
        Assert.Equal(2, result.Count);
        Assert.All(result, a => Assert.Equal("技术", a.Category));
    }

    public void Dispose()
    {
        _inMemoryDb.Dispose();
    }
}

Service 层测试策略

Service 层架构与依赖关系

Service 层通过依赖注入获取 Repository 实例,实现业务逻辑。以 BlogArticleServices 为例:

public class BlogArticleServices : BaseServices<BlogArticle>
{
    private readonly IBaseRepository<BlogArticle> _blogArticleRepository;
    private readonly IBaseRepository<BlogArticleComment> _commentRepository;

    public BlogArticleServices(
        IBaseRepository<BlogArticle> blogArticleRepository,
        IBaseRepository<BlogArticleComment> commentRepository)
    {
        _blogArticleRepository = blogArticleRepository;
        _commentRepository = commentRepository;
    }

    // 业务方法示例
    public async Task<PageModel<BlogArticle>> GetArticlesWithComments(int pageIndex, int pageSize)
    {
        // 实现包含评论统计的文章分页查询
    }
}

Service 层测试的 Mock 策略

Service 层测试的关键是 Mock 其依赖的 Repository 接口:

public class BlogArticleServicesTests
{
    private readonly Mock<IBaseRepository<BlogArticle>> _mockArticleRepo;
    private readonly Mock<IBaseRepository<BlogArticleComment>> _mockCommentRepo;
    private readonly BlogArticleServices _service;

    public BlogArticleServicesTests()
    {
        // 初始化 Mock 对象
        _mockArticleRepo = new Mock<IBaseRepository<BlogArticle>>();
        _mockCommentRepo = new Mock<IBaseRepository<BlogArticleComment>>();
        
        // 初始化 Service
        _service = new BlogArticleServices(
            _mockArticleRepo.Object,
            _mockCommentRepo.Object
        );
    }
}

业务逻辑测试实例

以下是对文章发布业务逻辑的测试,包含权限验证、数据验证和事务处理:

[Fact]
public async Task PublishArticle_ValidData_ReturnsSuccess()
{
    // Arrange
    var articleDto = new ArticlePublishDto
    {
        Title = "测试文章",
        Content = "测试内容",
        Category = "技术",
        AuthorId = 123
    };

    // 设置 Repository Mock 行为
    _mockArticleRepo.Setup(r => r.Add(It.IsAny<BlogArticle>()))
                    .ReturnsAsync(1001);
                    
    _mockCommentRepo.Setup(r => r.Add(It.IsAny<BlogArticleComment>()))
                    .ReturnsAsync(2001);

    // Act
    var result = await _service.PublishArticle(articleDto);

    // Assert
    Assert.True(result.success);
    Assert.Equal(1001, result.data);
    
    // 验证事务行为:两个 Repository 方法都应被调用
    _mockArticleRepo.Verify(r => r.Add(It.Is<BlogArticle>(a => 
        a.Title == "测试文章" && a.AuthorId == 123)), Times.Once);
        
    _mockCommentRepo.Verify(r => r.Add(It.Is<BlogArticleComment>(c => 
        c.ArticleId == 1001 && c.Content == "系统自动评论:文章发布成功")), Times.Once);
}

[Theory]
[InlineData(null, "内容", "标题不能为空")]       // 标题为空
[InlineData("标题", null, "内容不能为空")]       // 内容为空
[InlineData("短标题", "内容", "标题长度不能少于10个字符")] // 标题过短
public async Task PublishArticle_InvalidData_ReturnsError(
    string title, string content, string expectedMessage)
{
    // Arrange
    var invalidDto = new ArticlePublishDto
    {
        Title = title,
        Content = content,
        Category = "技术",
        AuthorId = 123
    };

    // Act
    var result = await _service.PublishArticle(invalidDto);

    // Assert
    Assert.False(result.success);
    Assert.Equal(expectedMessage, result.msg);
    
    // 验证 Repository 方法未被调用
    _mockArticleRepo.Verify(r => r.Add(It.IsAny<BlogArticle>()), Times.Never);
}

复杂业务场景测试

对于包含状态流转的复杂业务,可使用状态模式设计测试用例:

public class ArticleWorkflowTests
{
    // 测试文章从"草稿"→"审核中"→"已发布"→"已归档"的完整流程
    [Fact]
    public async Task ArticleLifecycle_CompleteFlow_WorksCorrectly()
    {
        // Arrange
        var articleId = 1001;
        var mockRepo = new Mock<IBaseRepository<BlogArticle>>();
        
        // 设置初始状态为草稿
        mockRepo.Setup(r => r.QueryById(articleId))
                .ReturnsAsync(new BlogArticle { 
                    Id = articleId, 
                    Status = ArticleStatus.Draft 
                });
                
        // 设置状态更新行为
        mockRepo.Setup(r => r.Update(It.IsAny<BlogArticle>()))
                .ReturnsAsync(true);

        var service = new BlogArticleServices(mockRepo.Object, Mock.Of<IBaseRepository<BlogArticleComment>>());

        // Act - 执行状态流转
        var submitResult = await service.SubmitForReview(articleId);
        var approveResult = await service.ApproveArticle(articleId);
        var archiveResult = await service.ArchiveArticle(articleId);

        // Assert
        Assert.True(submitResult.success);
        Assert.True(approveResult.success);
        Assert.True(archiveResult.success);
        
        // 验证状态更新是否符合预期
        mockRepo.VerifySequence(r => {
            r.Update(It.Is<BlogArticle>(a => a.Status == ArticleStatus.PendingReview)),
            r.Update(It.Is<BlogArticle>(a => a.Status == ArticleStatus.Published)),
            r.Update(It.Is<BlogArticle>(a => a.Status == ArticleStatus.Archived))
        });
    }
}

测试覆盖率分析与优化

生成覆盖率报告

在测试项目目录执行以下命令生成覆盖率报告:

dotnet test /p:CollectCoverage=true /p:CoverletOutputFormat=opencover

覆盖率分析与优化策略

Blog.Core 项目的 Repository 层和 Service 层测试应达到以下覆盖率目标:

  • 方法覆盖率:≥ 90%
  • 分支覆盖率:≥ 85%
  • 行覆盖率:≥ 85%

以下是典型的低覆盖率场景及优化方案:

低覆盖率场景优化方案
异常处理逻辑使用 Assert.ThrowsAsync 测试异常情况
条件分支使用 TheoryInlineData 测试所有分支
边界条件添加针对 null 值、空集合、极值的测试用例
私有方法通过公共接口间接测试,或使用 PrivateObject 反射调用

覆盖率报告解读示例

+---------------------------+--------+--------+--------+
| Module                    | Line   | Branch | Method |
+---------------------------+--------+--------+--------+
| Blog.Core.Repository      | 89.5%  | 82.3%  | 94.1%  |
| Blog.Core.Services        | 85.2%  | 78.6%  | 90.3%  |
| Blog.Core.Common          | 72.8%  | 65.4%  | 81.5%  |
+---------------------------+--------+--------+--------+

针对 Blog.Core.Common 模块覆盖率较低的情况,可优先添加对 CacheHelperLogHelper 等通用工具类的测试。

高级测试技巧与最佳实践

测试驱动开发(TDD)实战

以开发"文章点赞"功能为例,演示 TDD 流程:

  1. 编写测试用例(先写测试,再写实现):
[Fact]
public async Task LikeArticle_NewLike_IncreasesCountAndCreatesRecord()
{
    // Arrange
    var articleId = 1001;
    var userId = 5001;
    
    _mockArticleRepo.Setup(r => r.QueryById(articleId))
                    .ReturnsAsync(new BlogArticle { Id = articleId, LikeCount = 10 });
                    
    _mockLikeRepo.Setup(r => r.Query(It.Is<Expression<Func<ArticleLike, bool>>>(
        expr => EF.Property<int>(expr.Parameters[0], "ArticleId") == articleId &&
               EF.Property<int>(expr.Parameters[0], "UserId") == userId)))
                 .ReturnsAsync(new List<ArticleLike>());
                 
    _mockLikeRepo.Setup(r => r.Add(It.IsAny<ArticleLike>()))
                 .ReturnsAsync(3001);

    // Act
    var result = await _service.LikeArticle(articleId, userId);

    // Assert
    Assert.True(result.success);
    Assert.Equal(11, result.data.likeCount);
    
    _mockArticleRepo.Verify(r => r.Update(It.Is<BlogArticle>(a => 
        a.Id == articleId && a.LikeCount == 11)), Times.Once);
}
  1. 实现核心功能
public async Task<ApiResponse<ArticleLikeResult>> LikeArticle(int articleId, int userId)
{
    // 1. 验证文章是否存在
    var article = await _blogArticleRepository.QueryById(articleId);
    if (article == null)
        return new ApiResponse<ArticleLikeResult>(false, "文章不存在");
        
    // 2. 检查用户是否已点赞
    var existingLike = await _articleLikeRepository.Query(
        l => l.ArticleId == articleId && l.UserId == userId);
        
    if (existingLike.Any())
        return new ApiResponse<ArticleLikeResult>(false, "您已点赞该文章");
        
    // 3. 创建点赞记录
    var likeId = await _articleLikeRepository.Add(new ArticleLike 
    { 
        ArticleId = articleId, 
        UserId = userId,
        LikeTime = DateTime.Now 
    });
    
    // 4. 更新文章点赞数
    article.LikeCount++;
    await _blogArticleRepository.Update(article);
    
    return new ApiResponse<ArticleLikeResult>(new ArticleLikeResult 
    { 
        articleId = articleId,
        likeCount = article.LikeCount,
        likeId = likeId
    });
}
  1. 重构与优化(保持测试通过的前提下改进代码)

测试数据管理策略

  1. 使用测试数据构建器
public class BlogArticleBuilder
{
    private readonly BlogArticle _article = new BlogArticle();
    
    public BlogArticleBuilder WithTitle(string title)
    {
        _article.Title = title;
        return this;
    }
    
    public BlogArticleBuilder WithContent(string content)
    {
        _article.Content = content;
        return this;
    }
    
    public BlogArticleBuilder Published()
    {
        _article.Status = ArticleStatus.Published;
        _article.PublishTime = DateTime.Now;
        return this;
    }
    
    public BlogArticle Build() => _article;
}

// 使用方式
var testArticle = new BlogArticleBuilder()
    .WithTitle("测试文章")
    .WithContent("测试内容")
    .Published()
    .Build();
  1. 共享测试数据
public static class TestData
{
    public static BlogArticle GetTestArticle() => new BlogArticle 
    {
        Id = 1,
        Title = "共享测试文章",
        Content = "这是在多个测试中共享的测试数据",
        Category = "测试",
        Status = ArticleStatus.Published,
        CreateTime = new DateTime(2023, 1, 1),
        LikeCount = 5
    };
    
    public static IEnumerable<object[]> InvalidArticleData => new List<object[]>
    {
        new object[] { null, "内容", "标题不能为空" },
        new object[] { "短标题", "内容", "标题长度不能少于10个字符" },
        new object[] { "有效标题", null, "内容不能为空" }
    };
}

// 在测试中使用
[Theory]
[MemberData(nameof(TestData.InvalidArticleData))]
public async Task PublishArticle_InvalidData_ReturnsError(
    string title, string content, string expectedMessage)
{
    // 测试实现...
}

测试性能优化

随着测试用例增多,测试执行时间会变长,可采用以下优化策略:

  1. 使用集合 fixture 共享测试上下文:
[CollectionDefinition("DatabaseCollection")]
public class DatabaseCollection : ICollectionFixture<InMemoryDatabaseFixture>
{
    // 此 class 为空,仅用于标识集合
}

[Collection("DatabaseCollection")]
public class BlogArticleRepositoryTests
{
    private readonly InMemoryDatabaseFixture _fixture;
    
    public BlogArticleRepositoryTests(InMemoryDatabaseFixture fixture)
    {
        _fixture = fixture;
    }
    
    // 测试方法...
}
  1. 并行执行测试

xunit.runner.json 中配置:

{
  "parallelizeAssembly": true,
  "parallelizeTestCollections": true
}
  1. 分类执行测试

使用特性标记不同类型的测试:

[Fact, Trait("Category", "Unit")]
public void FastUnitTest() { /* 快速单元测试 */ }

[Fact, Trait("Category", "Integration")]
public void SlowIntegrationTest() { /* 慢速集成测试 */ }

执行时可指定分类:

dotnet test --filter Category=Unit  # 只执行单元测试

常见问题与解决方案

测试中遇到的典型问题

问题解决方案
Mock 复杂表达式树使用 It.Is<Expression<Func<T, bool>>>(expr => ...) 配合表达式解析
静态方法依赖使用 wrapper 模式封装静态方法,再对 wrapper 进行 Mock
时间依赖注入 IClock 接口而非直接使用 DateTime.Now
随机数依赖注入 IRandomGenerator 接口,测试时使用固定种子

复杂场景测试示例

问题:如何测试包含缓存逻辑的 Service 方法?

解决方案

[Fact]
public async Task GetArticleById_WithCache_ReturnsCachedData()
{
    // Arrange
    var articleId = 1001;
    var cacheKey = $"article:{articleId}";
    
    // 设置缓存行为:第一次为空,第二次返回缓存数据
    _mockCacheService.SetupSequence(s => s.Get<BlogArticle>(cacheKey))
                     .Returns((BlogArticle)null)
                     .Returns(new BlogArticle { Id = articleId, Title = "缓存文章" });
                     
    // 设置 Repository 行为
    _mockArticleRepo.Setup(r => r.QueryById(articleId))
                    .ReturnsAsync(new BlogArticle { Id = articleId, Title = "原始文章" });
    
    // Act - 第一次调用:应从数据库获取并设置缓存
    var firstResult = await _service.GetArticleById(articleId);
    
    // Assert - 第一次调用验证
    Assert.Equal("原始文章", firstResult.data.Title);
    _mockArticleRepo.Verify(r => r.QueryById(articleId), Times.Once);
    _mockCacheService.Verify(s => s.Set(cacheKey, It.IsAny<BlogArticle>(), It.IsAny<int>()), Times.Once);
    
    // Act - 第二次调用:应从缓存获取
    var secondResult = await _service.GetArticleById(articleId);
    
    // Assert - 第二次调用验证
    Assert.Equal("缓存文章", secondResult.data.Title);
    _mockArticleRepo.Verify(r => r.QueryById(articleId), Times.Once); // 验证 Repository 未被调用第二次
}

总结与进阶学习路径

关键知识点回顾

本文系统讲解了 Blog.Core 项目中 Repository 层和 Service 层的单元测试方法,核心要点包括:

  • 测试隔离:使用 Moq 隔离外部依赖,实现"无数据库"测试
  • 分层测试策略:对 Repository 层和 Service 层采用不同测试方法
  • 测试用例设计:基于等价类划分和边界值分析设计测试用例
  • 覆盖率优化:通过分析覆盖率报告持续改进测试质量
  • TDD 实践:先写测试再实现功能,提升代码设计质量

进阶学习路径

  1. 行为驱动开发(BDD):学习使用 SpecFlow 框架,用自然语言描述测试场景
  2. 契约测试:了解 Pact 框架,确保微服务间接口兼容性
  3. 性能测试:学习使用 BenchmarkDotNet 进行代码性能基准测试
  4. 持续集成:配置 CI/CD 流水线,实现测试自动化执行
  5. 混沌工程:学习在测试环境中注入故障,验证系统弹性

最佳实践清单

  • 每个测试方法只测试一个逻辑点
  • 使用清晰的测试命名:MethodName_Scenario_ExpectedResult
  • 保持测试独立性:测试间不共享状态
  • 优先测试行为而非实现细节
  • 定期重构测试代码,保持测试可读性
  • 将测试视为代码文档,通过测试理解系统行为

通过本文介绍的测试方法和实践技巧,你可以为 Blog.Core 项目构建一套完善的单元测试体系,显著提升代码质量和开发效率。记住,高质量的测试不仅是代码的安全网,也是设计思路的具体体现,更是团队协作的重要保障。

附录:常用测试代码片段

Moq 常用操作速查表

操作代码示例
设置方法返回值mock.Setup(m => m.Method()).Returns(42);
设置异步方法返回值mock.Setup(m => m.MethodAsync()).ReturnsAsync(42);
验证方法调用次数mock.Verify(m => m.Method(), Times.Once);
捕获方法参数var arg = It.IsAny<MyClass>(); mock.Setup(m => m.Method(arg)).Callback<MyClass>(a => a.Property = 5);
抛出异常mock.Setup(m => m.Method()).Throws<InvalidOperationException>();
匹配特定参数mock.Setup(m => m.Method(It.Is<int>(i => i > 0))).Returns(true);

测试数据生成工具

推荐使用 Bogus 库生成测试数据:

private readonly Faker<BlogArticle> _articleFaker = new Faker<BlogArticle>()
    .RuleFor(a => a.Title, f => f.Lorem.Sentence(3))
    .RuleFor(a => a.Content, f => f.Lorem.Paragraphs(3))
    .RuleFor(a => a.Category, f => f.PickRandom("技术", "生活", "职场"))
    .RuleFor(a => a.CreateTime, f => f.Date.Past(1))
    .RuleFor(a => a.Status, f => f.PickRandom<ArticleStatus>());

// 使用方式
var testArticles = _articleFaker.Generate(10); // 生成10篇随机文章

通过这些工具和实践,你可以为 Blog.Core 项目构建一套专业、高效的单元测试体系,在保障代码质量的同时,提升开发效率和重构信心。记住,测试不是额外的工作,而是高质量软件开发的必要投资。

【免费下载链接】Blog.Core 💖 ASP.NET Core 8.0 全家桶教程,前后端分离后端接口,vue教程姊妹篇,官方文档: 【免费下载链接】Blog.Core 项目地址: https://gitcode.com/gh_mirrors/bl/Blog.Core

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值