zoukankan      html  css  js  c++  java
  • ASP.NET Core 入门(3)(单元测试Xunit及Shouldly的使用)

    一、本篇简单介绍下在ASP.NET Core项目如何使用单元测试,例子是使用VS自带的Xunit来测试Web API接口,加上一款开源的断言工具Shouldly,方便写出更简洁、可读行更好的测试代码。

    1、添加xUnit项目

      由于我使用VS Code开发,所以操作是按VS Code的来,右键项目选择“Add new project”,接着选择“XUnit test project” 回车即可。可以看到引用了三个包,除此之外,还需要添加Microsoft.AspNetCore.App、Microsoft.AspNetCore.TestHost这两个包,另外我们再添加Shouldly的包。这样xUnit项目就建好了。

    2、编写单元测试

      对于接口怎么进行单元测试呢,一般做法都是针对接口项目的具体情况编写,比如封装测试基类,这里简单介绍基本的测试单元写法。

    测试接口,需要注意做好两点,调用时怎么传参,测试结果怎么检验。对于接口具体方法传参,这个比较好处理,是什么就模拟什么数据,但如果接口的Controller构造函数带参数,比如有注入,那么这里就需要在调用的时候构建一样的注入参数。对于测试结果断言,我们可以针对接口的统一返参格式进行封装断言,这里用上Shouldly来封装。具体的看代码。

    接口代码:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using DYDGame.Application;
    using DYDGame.Application.DTOs;
    using DYDGame.Utility;
    using DYDGame.Web.Host;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.Extensions.Options;
    using Newtonsoft.Json;
    using Newtonsoft.Json.Linq;
    
    namespace DYDGame.Web.Host.Controllers {
        /// <summary>
        /// 问答接口
        /// </summary>
        [Route ("api/[controller]")]
        [ApiController]
        public class QuestionController : ControllerBase {
            private APIConfig _apiConfig;
            private QuestionService _questionService;
            private string _connectionString;
    
            public QuestionController (IOptions<APIConfig> apiConfig) {
                _apiConfig = apiConfig.Value;
                _connectionString = _apiConfig.RDSExternalConStrAESDecrypt ();
                _questionService = new QuestionService (_connectionString);
            }
    
            /// <summary>
            /// 判断答题是否正确
            /// </summary>
            /// <param name="input"></param>
            [HttpPost ("JudgeAnswer")]
            public ResultObject JudgeAnswer (JudgeAnswerInput input) {
                dynamic obj = input;
                int questionId = obj.QuestionId;
                int answerId = obj.AnswerId;
                int flag = 0;
                try {
                    flag = _questionService.JudgeAnswer (questionId, answerId);
                } catch (System.Exception ex) {
                    Log4Net.LogInfo (_connectionString + ex.Message);
                }
    
                if (flag == -1) {
                    return ResultObject.Failure ("没有该条问题", ErrCode.NoData);
                } else if (flag == 1) {
                    return ResultObject.Ok ("恭喜答对了!", ErrCode.OK);
                } else {
                    return ResultObject.Failure ("答案错误");
                }
            }
        }
    }

    接口需要用到注入的配置参数

    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Linq;
    using DYDGame.Utility;
    using Microsoft.Extensions.Options;
    
    namespace DYDGame.Application
    {
        /// <summary>
        /// 读取appsettings.json的APIConfig
        /// </summary>
        /// <typeparam name="APIConfig"></typeparam>
        public class APIConfig: IOptions<APIConfig> {
            public APIConfig Value => this;
            public string ApiUrl { get; set; }
            public string OrgCode { get; set; }
            public string OrgKye { get; set; }
            public string RDSIntranetConStr { get; set; }
            public string RDSExternalConStr { get; set; }
        }
    
        public static class APIConfigModelExtension {
            public static string RDSIntranetConStrAESDecrypt (this APIConfig connectionStringModel) {
                return DESEncrypt.AESDecrypt (connectionStringModel.RDSIntranetConStr);
            }
            public static string RDSExternalConStrAESDecrypt (this APIConfig connectionStringModel) {
                return DESEncrypt.AESDecrypt (connectionStringModel.RDSExternalConStr);
            }
        }
    }

    测试基类

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Threading.Tasks;
    using DYDGame.Application;
    using DYDGame.Utility;
    
    namespace DYDGame.Tests {
        public class UnitBaseTest {
            private const string EncryptString = "N+edZ0vP9f5PdV1o7EkZgAbsowFPcQ7dUEx5W7DdJ5f30";
    
            //方便调用controller,controller构造函数需要注入APIConfig
            public static APIConfig OptionAPIConfig = new APIConfig { RDSExternalConStr = EncryptString };
    
        }
    }

    针对接口统一返回对象 ResultObject 进行断言封装

     /// <summary>
        /// 表示调用执行结果反馈
        /// </summary>
        public class ResultObject
        {
            #region 公共属性
    
            /// <summary>
            /// 调用是否成功,1成功0失败
            /// </summary>
            public string retStatus { get; set; }
    
            /// <summary>
            /// 调用响应代码:0000:成功;1112:参数不能为空;1118:请传入约定参数;1212:参数值错误;1116:失败;1123:接口出错;1124 查无数据
            /// </summary>
            public string errCode { get; set; }
    
            /// <summary>
            /// 调用响应消息
            /// </summary>
            public string errMsg { get; set; }
    
            /// <summary>
            /// 调用结果数据
            /// </summary>
            public object result { get; set; }
    }
    using Shouldly;
    using System;
    using DYDGame.Web.Host;
    
    namespace DYDGame.Tests.Extensions
    {
        public static class ApiResultObjectExtensions
        {
           
            /// <summary>
            /// ResultObject["retStatus"]
            /// </summary>
            /// <param name="retObj"></param>
            /// <returns></returns>
            public static string Get_retStatus(this ResultObject retObj)
            {
                return retObj.retStatus;
            }
    
            /// <summary>
            /// ResultObject["errMsg"]
            /// </summary>
            /// <param name="retObj"></param>
            /// <returns></returns>
            public static string Get_errMsg(this ResultObject retObj)
            {
                return retObj.errMsg;
            }
    
            /// <summary>
            /// ResultObject["errCode"]
            /// </summary>
            /// <param name="retObj"></param>
            /// <returns></returns>
            public static string Get_errCode(this ResultObject retObj)
            {
                return retObj.errCode;
            }
           
            /// <summary>
            /// ResultObject["result"]
            /// </summary>
            /// <param name="retObj"></param>
            /// <returns></returns>
            public static string Get_result(this ResultObject retObj)
            {
                return retObj.result.ToString();
            }
    
            /// <summary>
            /// 显示ResultObject中状态字符串
            /// </summary>
            /// <param name="retObj"></param>
            /// <returns></returns>
            public static string Show_StatusCodeMsg(this ResultObject retObj)
            {
                return string.Format("retStatus:{0},errCode:{1},errMsg:{2}", retObj.Get_retStatus(), retObj.Get_errCode(), retObj.Get_errMsg());
            }
    
            /// <summary>
            /// 显示ResultObject中状态字符串以及result
            /// </summary>
            /// <param name="retObj"></param>
            /// <returns></returns>
            public static string Show_StatusCodeMsg_And_result(this ResultObject retObj)
            {
                return string.Format("retStatus:{0},errCode:{1},errMsg:{2}|{3}",
                    retObj.Get_retStatus(), retObj.Get_errCode(),
                    retObj.Get_errMsg(), retObj.Get_result());
            }
            
            /// <summary>
            /// 断言retStatus等于"1",或 显示ResultObject中状态字符串以及result
            /// </summary>
            /// <param name="retObj"></param>
            public static void retStatus_ShouldBe_1(this ResultObject retObj)
            {
                retObj.retStatus.ShouldBe("1", retObj.Show_StatusCodeMsg_And_result());
            }
    
            /// <summary>
            /// 断言retStatus等于期望值
            /// </summary>
            /// <param name="retObj"></param>
            /// <param name="expected"></param>
            public static void retStatus_ShouldBe(this ResultObject retObj, string expected)
            {
                retObj.retStatus.ShouldBe(expected, retObj.Show_StatusCodeMsg_And_result());
            }
        }
    }

    编写测试用例

    using System;
    using System.Collections.Generic;
    using DYDGame.Application.DTOs;
    using DYDGame.Tests.Extensions;
    using DYDGame.Web.Host;
    using DYDGame.Web.Host.Controllers;
    using Xunit;
    using DYDGame.Application;
    using Microsoft.AspNetCore;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.Extensions.Configuration;
    
    namespace DYDGame.Tests {
        public class QuestionControllerTest : UnitBaseTest {
    
            private readonly QuestionController controller = new QuestionController (OptionAPIConfig);
    
            public static IEnumerable<object[]> JudgeAnswer_TestData () {
    
                var objValue = new JudgeAnswerInput ();
                objValue.QuestionId = 1;
                objValue.AnswerId = 0;
    
                yield return new object[] { objValue };
            }
    
            [Xunit.Theory (DisplayName = "判断答题是否正确 JudgeAnswer()")]
            [Xunit.MemberData ("JudgeAnswer_TestData")]
            [Xunit.Trait ("业务", "答题")]
            [Xunit.Trait ("By", "robin")]
            public void JudgeAnswer_Test (JudgeAnswerInput input) {
                var result = controller.JudgeAnswer (input);
    
                //验证返回的结果状态是否等于1
                result.retStatus_ShouldBe_1 ();
            }
        }
    }

    右键xUnit项目,选择Test 即可运行测试。

  • 相关阅读:
    15调度
    如何在idea中找到通过依赖添加的jar包位置
    验证码实现步骤
    重构:利用postman检测前后端互相传值
    反射机制
    Unexpected update count received. Changes will be rolled back. SQL: DELETE FROM `myproject`.`role_module` WHERE `role_id` = ? AND `module_id` = ?
    JavaSE基础之 IO_Buffer
    JavaSE基础之 IO流
    JavaSE基础之 XML(可扩展标记语言)
    JavaSE基础之继承
  • 原文地址:https://www.cnblogs.com/wybin6412/p/11010919.html
Copyright © 2011-2022 走看看