一、本篇简单介绍下在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. CORTEX-M3中断的现场保护问题

    在<Cortex-M3 Devices Generic User Guide.pdf>中介绍了异常入栈和出栈的情况,详见2.3 Exception model.Cortex-M3内核的寄存 ...

  2. Keil综合(03)_map文件全解析[转]

    推荐分享一个大神的人工智能教程.零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到人工智能的队伍中来!http://www.captainbed.net/strongerhuang 我的网站:ht ...

  3. MySQL实时监听——EPX 原创: 奕X 贝壳产品技术 今天

    MySQL实时监听——EPX 原创: 奕X 贝壳产品技术 今天

  4. Centos7.4服务器安装Laravel5.7详细讲解(2018-10-27)

    一.在阿里云或者腾讯云选择Centos7并购买服务器 二.安装宝塔面板和php运行环境 1.输入命令 yum install -y wget && wget -O install.sh ...

  5. Angular 执行 css3 简单的动画

    <div class="content"> 内容区域 <button (click)="showAside()">弹出侧边栏</b ...

  6. Linux 下kafka集群搭建

    主机的IP地址: 主机IP地址 zookeeper kafka10.19.85.149 myid=1 broker.id=110.19.15.103 myid=2 broker.id=210.19.1 ...

  7. starUML建模C++【逆向工程】

    1.下载starUML 2.打开starUML,选择default approach 3.添加 Profile,把C++添加进去 4.在右侧的工程上点右键—[C++]—-[Reverse Engine ...

  8. css简单学习属性3---css属性选择器

    1:通配符 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF ...

  9. Python2和Python3的字符串编码和类型

    一.字符串编码和类型 任何编码格式的字符串,都可以和Unicode互相转换. gbk -> utf8 # 将字符串按指定格式进行解码,返回Unicode字符串unicode_str = gbk_ ...

  10. git 关于Git每次进入都需要输入用户名和密码的问题解决

    解决办法: git bash进入你的项目目录,输入: git config --global credential.helper store 然后你会在你本地生成一个文本,上边记录你的账号和密码.当然 ...