一、本篇简单介绍下在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 = ;
try {
flag = _questionService.JudgeAnswer (questionId, answerId);
} catch (System.Exception ex) {
Log4Net.LogInfo (_connectionString + ex.Message);
} if (flag == -) {
return ResultObject.Failure ("没有该条问题", ErrCode.NoData);
} else if (flag == ) {
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("", 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 = ;
objValue.AnswerId = ; 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 即可运行测试。

ASP.NET Core 入门(3)(单元测试Xunit及Shouldly的使用)的更多相关文章

  1. ASP.NET Core入门(一)

    大家好,很荣幸您点了开此篇文章,和我一起来学习ASP.NET Core,此篇文字为<ASP.NET Core入门>系列中的第一篇,本系列将以一个博客系统为例,从第一行代码,到系统发布上线( ...

  2. CentOS开发ASP.NET Core入门教程

    作者:依乐祝 原文地址:https://www.cnblogs.com/yilezhu/p/9891346.html 因为之前一直没怎么玩过CentOS,大多数时间都是使用Win10进行开发,然后程序 ...

  3. ASP.NET Core 入门教程 10、ASP.NET Core 日志记录(NLog)入门

    一.前言 1.本教程主要内容 ASP.NET Core + 内置日志组件记录控制台日志 ASP.NET Core + NLog 按天记录本地日志 ASP.NET Core + NLog 将日志按自定义 ...

  4. 【翻译】ASP.NET Core 入门

    ASP.NET Core 入门 原文地址:Introduction to ASP.NET Core         译文地址:asp.net core 简介           翻译:ganqiyin ...

  5. ASP.NET CORE 入门教程(附源码)

    ASP.NET CORE 入门教程 第一课 基本概念 基本概念 Asp.Net Core Mvc是.NET Core平台下的一种Web应用开发框架 符合Web应用特点 .NET Core跨平台解决方案 ...

  6. Asp.net Core 入门实战

    Asp.Net Core 是开源,跨平台,模块化,快速而简单的Web框架. Asp.net Core官网的一个合集,方便一次性Clone 目录 快速入门 安装 一个最小的应用 项目模板 路由 静态文件 ...

  7. Asp.Net Core 轻松学-利用xUnit进行主机级别的网络集成测试

    前言     在开发 Asp.Net Core 应用程序的过程中,我们常常需要对业务代码编写单元测试,这种方法既快速又有效,利用单元测试做代码覆盖测试,也是非常必要的事情:但是,但我们需要对系统进行集 ...

  8. ASP.NET Core 入门教程 2、使用ASP.NET Core MVC框架构建Web应用

    一.前言 1.本文主要内容 使用dotnet cli创建基于解决方案(sln+csproj)的项目 使用Visual Studio Code开发基于解决方案(sln+csproj)的项目 Visual ...

  9. 转载: ASP.NET Core入门系列文章

    今天在网上发现了ithome上的asp.net core 系列文章,对于新手入门还不错,这里转载一下,也方便查阅. [Day01] 從頭開始 [Day02] 程式生命週期 (Application L ...

随机推荐

  1. 2019-8-15C#MDI窗体实现多窗口效果

    C#MDI窗体实现多窗口效果  Visual C#是微软公司推出的下一代主流程序开发语言,他也是一种功能十分强大的程序设计语言,正在受到越来越多的编程人员的喜欢.在Visual C#中,提供了为实现M ...

  2. C# GDI+ 简单实现图片写文字和图片叠加(水印)(转)

    using System; using System.Collections; using System.Configuration; using System.Data; using System. ...

  3. 【420】链表实现Quack

    quack.h // quack.h: an interface definition for a queue/stack #include <stdio.h> #include < ...

  4. iOS 判断scrollView是否滑动到底部

      判断scrollView有没有滚动到视图的底部,用来判断下拉刷新的时间.等 - (void)scrollViewDidScroll:(UIScrollView *)scrollView1 { CG ...

  5. Microsoft Visual Studio(VS)启动报安装过程中无法运行

    开机启动VS提示无法运行,很可能VS正在更新,可以等待几分钟更新完成,再次运行VS. 也可以把更新进程结束,进程名:VSIXAutoUpdate.exe

  6. redis配置用户认证密码

    1,下载安装 Download, extract and compile Redis with: $ wget http://download.redis.io/releases/redis-3.2. ...

  7. C#线程 BeginInvoke和EndInvoke使用方法

    一.C#线程概述 在操作系统中一个进程至少要包含一个线程,然后,在某些时候需要在同一个进程中同时执行多项任务,或是为了提供程序的性能,将要执行的任务分解成多个子任务执行.这就需要在同一个进程中开启多个 ...

  8. 汉化Git Gui

    先来一个图: 其实使用自带的图形化操作git还是很方便的,但是英文界面还是让相当一部分小伙伴不太舒服,比如我哈. 汉化包地址:链接: https://pan.baidu.com/s/1qQtBUsf1 ...

  9. 锚点/JQ:点击导航跳到网页中的指定位置

    今天做了一个简单的功能,页面往下滚动到一定位置,顶部出现一个浮动的导航栏,点击导航栏标签,下面页面跳转到相应的区域.回到顶部,导航栏隐藏. 因为顶部有一个浮动的导航栏,所以跳转到下面页面的时候,总是盖 ...

  10. git 提交大小超过100M

    #MsnDialog.ad, #MyMoveAd, #QQ_Full, #ad-SNSSplashAd, #ad6cn, #adBody07, #adLeftFloat, #adRightFloat, ...