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 = ;
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的使用)的更多相关文章
- ASP.NET Core入门(一)
大家好,很荣幸您点了开此篇文章,和我一起来学习ASP.NET Core,此篇文字为<ASP.NET Core入门>系列中的第一篇,本系列将以一个博客系统为例,从第一行代码,到系统发布上线( ...
- CentOS开发ASP.NET Core入门教程
作者:依乐祝 原文地址:https://www.cnblogs.com/yilezhu/p/9891346.html 因为之前一直没怎么玩过CentOS,大多数时间都是使用Win10进行开发,然后程序 ...
- ASP.NET Core 入门教程 10、ASP.NET Core 日志记录(NLog)入门
一.前言 1.本教程主要内容 ASP.NET Core + 内置日志组件记录控制台日志 ASP.NET Core + NLog 按天记录本地日志 ASP.NET Core + NLog 将日志按自定义 ...
- 【翻译】ASP.NET Core 入门
ASP.NET Core 入门 原文地址:Introduction to ASP.NET Core 译文地址:asp.net core 简介 翻译:ganqiyin ...
- ASP.NET CORE 入门教程(附源码)
ASP.NET CORE 入门教程 第一课 基本概念 基本概念 Asp.Net Core Mvc是.NET Core平台下的一种Web应用开发框架 符合Web应用特点 .NET Core跨平台解决方案 ...
- Asp.net Core 入门实战
Asp.Net Core 是开源,跨平台,模块化,快速而简单的Web框架. Asp.net Core官网的一个合集,方便一次性Clone 目录 快速入门 安装 一个最小的应用 项目模板 路由 静态文件 ...
- Asp.Net Core 轻松学-利用xUnit进行主机级别的网络集成测试
前言 在开发 Asp.Net Core 应用程序的过程中,我们常常需要对业务代码编写单元测试,这种方法既快速又有效,利用单元测试做代码覆盖测试,也是非常必要的事情:但是,但我们需要对系统进行集 ...
- ASP.NET Core 入门教程 2、使用ASP.NET Core MVC框架构建Web应用
一.前言 1.本文主要内容 使用dotnet cli创建基于解决方案(sln+csproj)的项目 使用Visual Studio Code开发基于解决方案(sln+csproj)的项目 Visual ...
- 转载: ASP.NET Core入门系列文章
今天在网上发现了ithome上的asp.net core 系列文章,对于新手入门还不错,这里转载一下,也方便查阅. [Day01] 從頭開始 [Day02] 程式生命週期 (Application L ...
随机推荐
- CORTEX-M3中断的现场保护问题
在<Cortex-M3 Devices Generic User Guide.pdf>中介绍了异常入栈和出栈的情况,详见2.3 Exception model.Cortex-M3内核的寄存 ...
- Keil综合(03)_map文件全解析[转]
推荐分享一个大神的人工智能教程.零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到人工智能的队伍中来!http://www.captainbed.net/strongerhuang 我的网站:ht ...
- MySQL实时监听——EPX 原创: 奕X 贝壳产品技术 今天
MySQL实时监听——EPX 原创: 奕X 贝壳产品技术 今天
- Centos7.4服务器安装Laravel5.7详细讲解(2018-10-27)
一.在阿里云或者腾讯云选择Centos7并购买服务器 二.安装宝塔面板和php运行环境 1.输入命令 yum install -y wget && wget -O install.sh ...
- Angular 执行 css3 简单的动画
<div class="content"> 内容区域 <button (click)="showAside()">弹出侧边栏</b ...
- Linux 下kafka集群搭建
主机的IP地址: 主机IP地址 zookeeper kafka10.19.85.149 myid=1 broker.id=110.19.15.103 myid=2 broker.id=210.19.1 ...
- starUML建模C++【逆向工程】
1.下载starUML 2.打开starUML,选择default approach 3.添加 Profile,把C++添加进去 4.在右侧的工程上点右键—[C++]—-[Reverse Engine ...
- css简单学习属性3---css属性选择器
1:通配符 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF ...
- Python2和Python3的字符串编码和类型
一.字符串编码和类型 任何编码格式的字符串,都可以和Unicode互相转换. gbk -> utf8 # 将字符串按指定格式进行解码,返回Unicode字符串unicode_str = gbk_ ...
- git 关于Git每次进入都需要输入用户名和密码的问题解决
解决办法: git bash进入你的项目目录,输入: git config --global credential.helper store 然后你会在你本地生成一个文本,上边记录你的账号和密码.当然 ...