.Net单元测试业务实践

简介: code[class*="language-"],pre[class*="languag...

业务简述

  • 关键字段:邀请码最大使用次数UseMaxNumber和允许取消次数CancelUseMaxNumber,已使用次数UsedCount,已取消次数CancelUsedCount。

  • 提交使用邀请码的订单,占用邀请码使用次数。
    在允许取消次数内取消订单,退回邀请码使用次数。
    超过允许取消次数取消订单,不退回邀请码使用次数。

  • 注意点:临界值。

原核心代码(X.1版)

public ResponseMessage<bool> 示例方法_ProcessCode(X used,YY invitecodedto)
{
  var isoverinvite = false;//已经超过取消次数
  var iswilloverinvite = false;//将要超出取消次数
  long inviteNum = 0;//本次邀约使用次数
  //判断是否已经超过取消次数,或者将要超出取消次数。
  if (invitecodedto != null && invitecodedto.IsLimitCancelUse)
  {
      if (invitecodedto.CancelUsedCount > invitecodedto.CancelUseMaxNumber)
      {
          isoverinvite = true;
      }
      else if (invitecodedto.CancelUsedCount + used.InviteNum > invitecodedto.CancelUseMaxNumber)
      {
          iswilloverinvite = true;
      }
  }

  ResponseMessage<long> inviteuseres = null;
  //邀约码不为null,递增取消次数,扣减使用次数。
  if (invitecodedto != null)
  {
      //递增已取消次数
      var cancelcount = _codeService.IncCancelUseCount(invitecodedto.Id, (int)used.InviteNum);
      if (isoverinvite)
      {

      }
      else if (iswilloverinvite)
      {
          inviteNum = invitecodedto.CancelUseMaxNumber > cancelcount.Body ? invitecodedto.CancelUseMaxNumber - cancelcount.Body : cancelcount.Body - invitecodedto.CancelUseMaxNumber;
          //将要超出的,只退出部分。
          inviteuseres = _codeService.IncUseCount(invitecodedto.Id, -(int)(inviteNum));
      }
      else
      {
          inviteNum = used.InviteNum;
          //未超出取消次数的,全数退回。
          inviteuseres = _codeService.IncUseCount(invitecodedto.Id, -(int)inviteNum);
      }
  }
  .
  .
  .
  //更新取消日志。
  //更新码相关的各种状态。
}

X.1版代码引起问题

  • 使用次数为1,允许取消次数为1时,运行正确。

  • 使用次数为1,允许取消次数为2时,结果错误。

     >>测试流程目标:【每次报名都为1人】报名一次,取消一次,再报名一次,再取消一次后。再报名一次后,后续不能再报名。
     >>实际效果:仍然还能报名一次。
     >>原因分析:订单第二次取消后。已取消次数为2,允许取消次数为2,这个判断无法命中。   
     if (invitecodedto.CancelUsedCount > invitecodedto.CancelUseMaxNumber)
         {
             isoverinvite = true;
         }
    

优化后代码(X.2版)

 var isoverinvite = false;//已经超过取消次数
 var iswilloverinvite = false;//将要超出取消次数
 long inviteNum = 0;//本次邀约使用次数
 if (invitecodedto != null && invitecodedto.IsLimitCancelUse)
 {
     //这里多加了个=号
     if (invitecodedto.CancelUsedCount >= invitecodedto.CancelUseMaxNumber)
     {
         isoverinvite = true;
     }//这里也多加了个=号
     else if (invitecodedto.CancelUsedCount + used.InviteNum >= invitecodedto.CancelUseMaxNumber)
     {
         iswilloverinvite = true;
     }
 }

X.2版代码引起问题

  • X.2版修复了上个问题。但仍有场景覆盖不够。

  • 使用次数为2,允许取消次数为2时,结果错误。

    >>测试流程目标:报名一次(1人),取消,再报名一次(2人),再取消。预期仍可以继续报名1人。
    >>实际效果:无法继续报名。
    >>原因分析,第二次取消请求时:
    >>>根据判断 已取消次数加上邀约人数大于允许取消次数,1+2>2,所以是将要超出允许取消次数。
    .
    .
        else if (invitecodedto.CancelUsedCount + used.InviteNum > invitecodedto.CancelUseMaxNumber)
        {
            iswilloverinvite = true;
        }
    .
    .
    >>>再来看下扣减使用次数的部分。CancelUseMaxNumber为2,cancelcount.Body为2>>>所以结果是:2>2?(2-2):(2-2),返回0,意思是没有返回使用次数。
    .
    .
        else if (iswilloverinvite)
        {
            inviteNum = invitecodedto.CancelUseMaxNumber > cancelcount.Body ? invitecodedto.CancelUseMaxNumber - cancelcount.Body : cancelcount.Body - invitecodedto.CancelUseMaxNumber;
            //将要超出的,只退出部分。
            inviteuseres = _codeService.IncUseCount(invitecodedto.Id, -(int)(inviteNum));
        }
    .
    .
    >>>正确结果应该是:因为已经取消过一次了,这次报名2人,如按正常应该是总取消3次,但允许取消次数是2次,所以使用次数只能返回一次。
    >>>预期结果和实际结果不符。

思考

  • 上面问题是由于退回使用次数计算不对引起的。

  • 改动后验证流程是很繁琐的,要配置邀请码,要填写报名信息,要重复提交,重复取消订单好几次来验证逻辑。

  • 组合条件是千变万化的。

  • 这个业务重点是测试取消订单后对于使用次数和允许取消次数的正确性。如全流程走一下,是浪费时间的。

  • 所以为保证正确性及方便,这个必须支持单元测试。单元测试才能快速试错。

影响单元测试的几点

  • 业务耦合。这个取消邀请方法内有处理邀请码使用次数和取消次数的,也有处理取消记录,维护各个状态等。不符合单一功能原则。

  • 数据库依赖,影响mock数据及执行后的结果对比。

  • 重复执行后结果的积累。如订单取消后,邀请码的使用次数和允许取消次数都会变,作为下次单元测试的依据。

改进建议

  • 对打算单元测试的代码,要保持功能单一,不耦合其他业务。

  • 面向接口编程,依赖注入。与具体的实现解耦,方便单元测试。

  • 方法体尽量移除仓储部分逻辑或者mock一个仓储对象替代。

  • 必须方便批量单元测试。

单元测试前置--Nuget包依赖

  • Xunit:一个开发测试框架,它支持测试驱动开发,具有极其简单和与框架特征对齐的设计目标。

  • xunit.runner.visualstudio: 支持Vs调试,运行测试

  • NSubstitute :一个友好的.net单元测试隔离框架。

  • Autofac: Ioc容器

//单元测试部分
public class GetTicketDiscounts_Test
    {       
        private IXTaDiscountService discountService = null;
        private IXTaCodeService codeSub = null;
        public GetTicketDiscounts_Test()
        {
            discountService = XTaContainer.Resolve<IXTaDiscountService>();
            codeSub = NSubstitute.Substitute.For<IXTaCodeService>();
        }
    }
//注册部分
 public static class XTaContainer
    {
        public readonly static IContainer _container;
        static XTaContainer()
        {
            // Create your builder.
            var builder = new ContainerBuilder();
            //自动注册。
            var baseType = typeof(IApplication);
            var assemblys = AppDomain.CurrentDomain.GetAssemblies().ToList();
  
            builder.RegisterAssemblyTypes(assemblys.ToArray())
                   .Where(t => baseType.IsAssignableFrom(t) && t != baseType)
                   .AsImplementedInterfaces()
                   .InstancePerLifetimeScope();
           //Redis
            builder.Register(n => Substitute.For<ICache>())
                .As<ICache>().SingleInstance();          
            //mongodb
            builder.Register(n => Substitute.For<IMongoDbProvider>())
                .As<IMongoDbProvider>().SingleInstance();
            _container = builder.Build();
        }
        public static T Resolve<T>()
        {
            return _container.Resolve<T>();
        }
    }

支持单元测试的代码(X.3版-只粘贴相关代码)

//接口
public interface IXTaService : IApplication{
    ResponseMessage<long> GetReturnUseNum(long invitediscountNum, XTaCodeDto codedto);
}
//实现
 public class XTaDiscountService : IXTaDiscountService
    {
        private readonly IXTaCodeService _codeService;
        public XTaDiscountService(
            IXTaCodeService codeService)
        {
            _codeService = codeService;
        }
        //将操作使用次数和取消次数的仓储部分挪出去,这里只计算需要退回的使用次数。
        public ResponseMessage<long> GetReturnUseNum(long invitediscountNum, XTaCodeDto codedto)
        {
            //默认是全部退回使用次数。
            long returnNum = invitediscountNum;
            if (codedto == null)
            {
                return ResponseMessage<long>.MakeSucc(0);
            }
            //不限制取消的的时候,退回全部使用次数。
            if (!codedto.IsLimitCancelUse)
            {
                return ResponseMessage<long>.MakeSucc(returnNum);
            }
            //已超过的不处理。
            if (codedto.CancelUsedCount >= codedto.CancelUseMaxNumber)
            {
                return ResponseMessage<long>.MakeSucc(0);
            }
            //将要超过的。
            if (codedto.CancelUsedCount + invitediscountNum >= codedto.CancelUseMaxNumber)
            {
                returnNum = codedto.CancelUsedCount + invitediscountNum - codedto.CancelUseMaxNumber;
                return ResponseMessage<long>.MakeSucc(returnNum);
            }
            return ResponseMessage<long>.MakeSucc(returnNum);
        }
    }
>初始化数据
  
    private void 验证取消优惠_初始化数据(ref XTaCodeDto codeDto, int usemax = 0, int cancelmax = 0)
    {
        if (codeDto == null)
        {
            codeDto = new XTaCodeDto()
            {
                Id = "11111",
                CancelUsedCount = 0,
                UsedCount = 0,
                PrivateSetting = new PrivateSetting()
                {
                    IsLimitCancelUse = true,
                    IsCustomCancelUse = true,
                    CancelUseMaxNumber = 1,
  
                    IsLimitUse = true,
                    IsCustomUse = true,
                    UseMaxNumber = 1
                }
            };
        }
        if (cancelmax > 0)
        {
            codeDto.PrivateSetting.CancelUseMaxNumber = cancelmax;
            codeDto.CancelUsedCount = 0;
        }
        if (usemax > 0)
        {
            codeDto.PrivateSetting.UseMaxNumber = usemax;
            codeDto.UsedCount = 0;
        }
    }
> 模拟报名使用邀请码,递增使用次数,方便批量测试。
  
    private void 初始化数据_模拟报名使用邀请码_递增使用次数(int useNum, XTaCodeDto codeDto)
    {
        //mock模拟使用邀请码时,递增的邀请码使用次数返回使用次数。
        var usercount = codeSub.IncUseCount(codeDto.Id, Arg.Any<int>()).Returns(x => new ResponseMessage<long>() { Body = (int)codeDto.UsedCount + x.Arg<int>() });
        codeDto.UsedCount = codeSub.IncUseCount(codeDto.Id, useNum).Body;
    }
 > 模拟取消订单,退回使用次数
  
    private void 验证取消优惠_退回使用次数_V1ForPrivate(long inviteDiscountNum, XTaCodeDto codeDto)
    {
        //计算退回使用次数。
        var res = discountService.GetReturnUseNum(inviteDiscountNum, codeDto);
        codeDto.UsedCount -= res.Body;
        codeDto.CancelUsedCount += inviteDiscountNum;
    }
>实际测试部分
  
    [Fact]
    public void 验证取消优惠_退回使用次数_最大使用一次_允许取消一次()
    {
        XTaCodeDto codeDto = null;
        验证取消优惠_初始化数据(ref codeDto, 1, 1);
  
        //第一次报名,取消
        验证取消优惠_模拟报名使用邀请码_递增使用次数(1, codeDto);
        验证取消优惠_退回使用次数_V1ForPrivate(1, codeDto);
        //第一次取消会退回使用次数。
        Assert.True(codeDto.UsedCount == 0 && codeDto.CancelUsedCount == 1);
  
        //第二次报名,取消
        验证取消优惠_模拟报名使用邀请码_递增使用次数(1, codeDto);
        验证取消优惠_退回使用次数_V1ForPrivate(1, codeDto);
        //第二次取消后,超出允许取消次数限制,不会退回
        Assert.True(codeDto.UsedCount == 1 && codeDto.CancelUsedCount == 2);
    }    
    [Fact]
    public void 验证取消优惠_退回使用次数_最大使用2次_允许取消两次()
    {
  
        XTaCodeDto codeDto = null;
        验证取消优惠_初始化数据(ref codeDto, 2, 2);
  
        验证取消优惠_模拟报名使用邀请码_递增使用次数(1, codeDto);
        验证取消优惠_退回使用次数_V1ForPrivate(1, codeDto);
        Assert.True(codeDto.UsedCount == 0 && codeDto.CancelUsedCount == 1);
  
  
        验证取消优惠_模拟报名使用邀请码_递增使用次数(2, codeDto);
        验证取消优惠_退回使用次数_V1ForPrivate(2, codeDto);
        Assert.True(codeDto.UsedCount == 1 && codeDto.CancelUsedCount == 3);
  
  
        验证取消优惠_模拟报名使用邀请码_递增使用次数(1, codeDto);
        验证取消优惠_退回使用次数_V1ForPrivate(1, codeDto);
        Assert.True(codeDto.UsedCount == 2 && codeDto.CancelUsedCount == 4);
    }

使用单元测试的好处

  • 快速验证结果,不用依赖各种数据库/缓存等环境。

  • 代码指责更单一。

  • 减少bug

  • 方便后期持续集成

可参考连接

使用 dotnet test 和 xUnit 在 .NET Core 中进行 C# 单元测试
nsubstitute 介绍
Autofac介绍
单元测试的艺术

作者:从此启程/范存威

出处:http://www.cnblogs.com/fancunwei/

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文链接。如文章对您有用,烦请点个推荐再走,感谢! 本博客新开通打赏,鼠标移到右侧打赏浮动处,即可赏博主点零花钱,感谢您的支持!

相关文章
|
6天前
|
数据可视化 JavaScript 前端开发
利用Postman和Apipost进行API测试的实践与优化-动态参数
在API测试中,Postman和Apipost是常用的工具。Postman内置变量功能有限,面对复杂场景时需编写JavaScript脚本,增加了维护成本。而Apipost提供丰富的内置变量、可视化动态值配置和低代码操作,支持生成真实随机数据,如邮箱、手机号等,显著提升测试效率和灵活性。对于复杂测试场景,Apipost是更好的选择,能有效降低开发与维护成本,提高测试工作的便捷性和可维护性。
|
25天前
|
JSON 前端开发 API
以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡
以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡
58 5
以项目登录接口为例-大前端之开发postman请求接口带token的请求测试-前端开发必学之一-如果要学会联调接口而不是纯写静态前端页面-这个是必学-本文以优雅草蜻蜓Q系统API为实践来演示我们如何带token请求接口-优雅草卓伊凡
|
3月前
|
算法 Java 测试技术
使用 BenchmarkDotNet 对 .NET 代码进行性能基准测试
使用 BenchmarkDotNet 对 .NET 代码进行性能基准测试
84 13
|
3月前
|
算法 Java 测试技术
Benchmark.NET:让 C# 测试程序性能变得既酷又简单
Benchmark.NET是一款专为 .NET 平台设计的性能基准测试框架,它可以帮助你测量代码的执行时间、内存使用情况等性能指标。它就像是你代码的 "健身教练",帮助你找到瓶颈,优化性能,让你的应用跑得更快、更稳!希望这个小教程能让你在追求高性能的路上越走越远,享受编程带来的无限乐趣!
172 13
|
3月前
|
监控 搜索推荐 测试技术
电商API的测试与用途:深度解析与实践
在电子商务蓬勃发展的今天,电商API成为连接电商平台、商家、消费者和第三方开发者的重要桥梁。本文深入探讨了电商API的核心功能,包括订单管理、商品管理、用户管理、支付管理和物流管理,并介绍了有效的测试技巧,如理解API文档、设计测试用例、搭建测试环境、自动化测试、压力测试、安全性测试等。文章还详细阐述了电商API的多样化用途,如商品信息获取、订单管理自动化、用户数据管理、库存同步、物流跟踪、支付处理、促销活动管理、评价管理、数据报告和分析、扩展平台功能及跨境电商等,旨在为开发者和电商平台提供有益的参考。
124 0
|
3月前
|
测试技术 Python
探索软件测试的深度与广度:从理论到实践
在数字化时代,软件已成为我们生活中不可或缺的一部分。随着技术的不断进步和用户需求的多样化,确保软件质量变得尤为重要。本文将深入浅出地介绍软件测试的核心概念、类型及其在软件开发生命周期中的重要性。我们将通过实际案例,展示如何实施有效的测试策略,并探讨自动化测试的未来趋势,旨在为读者提供一套完整的软件测试知识体系,帮助提升软件质量和开发效率。
|
3月前
|
数据采集 监控 机器人
浅谈网页端IM技术及相关测试方法实践(包括WebSocket性能测试)
最开始转转的客服系统体系如IM、工单以及机器人等都是使用第三方的产品。但第三方产品对于转转的业务,以及客服的效率等都产生了诸多限制,所以我们决定自研替换第三方系统。下面主要分享一下网页端IM技术及相关测试方法,我们先从了解IM系统和WebSocket开始。
76 4
|
3月前
|
测试技术 Python
探索软件测试的奥秘:从理论到实践
在软件开发的宇宙中,软件测试犹如一颗璀璨的星辰,指引着质量的方向。本文将带你穿梭于软件测试的理论与实践之间,揭示其内在的逻辑和魅力。从测试的重要性出发,我们将探讨不同类型的测试方法,并通过实际案例分析,深入理解测试用例的设计和应用。最后,我们将通过一个代码示例,展示如何将理论知识转化为实际操作,确保软件质量的同时,也提升你的测试技能。让我们一起踏上这段探索之旅,发现软件测试的无限可能。
|
3月前
|
人工智能 JavaScript 前端开发
自动化测试框架的演进与实践###
本文深入探讨了自动化测试框架从诞生至今的发展历程,重点分析了当前主流框架的优势与局限性,并结合实际案例,阐述了如何根据项目需求选择合适的自动化测试策略。文章还展望了未来自动化测试领域的技术趋势,为读者提供了宝贵的实践经验和前瞻性思考。 ###
|
3月前
|
测试技术
探索软件测试的奥秘:从理论到实践
本文深入探讨了软件测试的基本概念、重要性、主要类型以及实施策略。通过分析不同测试阶段和相应的测试方法,文章旨在为读者提供一套完整的软件测试知识体系,帮助他们更好地理解和应用测试技术,确保软件产品的质量和可靠性。
89 4

热门文章

最新文章