一、本篇简单介绍下在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. mysql插入数据时 insert IGNORE、ON DUPLICATE KEY UPDATE、replace into

    转: mysql insert时几个操作DELAYED .IGNORE.ON DUPLICATE KEY UPDATE的区别 博客分类: mysql基础应用   mysql insert时几个操作DE ...

  2. SSM配置基于注解AOP

    pom.xml <dependency> <groupId>org.springframework</groupId> <artifactId>spri ...

  3. LSTM改善RNN梯度弥散和梯度爆炸问题

    我们给定一个三个时间的RNN单元,如下: 我们假设最左端的输入  为给定值, 且神经元中没有激活函数(便于分析), 则前向过程如下: 在  时刻, 损失函数为  ,那么如果我们要训练RNN时, 实际上 ...

  4. 添加zookeeper到服务,并设置开机启动

    一.先安装jdk jdk 路径为/usr/local/java 二.再安装zookeeper zk路径为/use/local/zookeeper 三.创建zookeeper脚本 cd /etc/rc. ...

  5. 【c# 学习笔记】委托的使用

    //委托使用的演示 class Program { //1.使用delegate关键字来定义一个委托类型 public delegate void MyDelegate(int para1, int ...

  6. 记录一下我的git连接不上GitHub问题

    1.日常操作,提交代码,报错误下: $ git clone git@github.com:hanchao5272/myreflect.git Cloning into 'myreflect'... s ...

  7. Java程序的编写与执行、Java新手常见问题及解决方法|乐字节Java学习

    今天,我们来写一段Java程序.然后看看Java程序是如何执行的,以及Java新手小白遇到的问题和解决办法.   一.HelloWorld的编写 ① 新建一个XXX.java (文件的扩展名显示出来) ...

  8. elasticsearch-5.6.1删除index下的某个type

    由于elasticsearch-5.6.1不支持type直接删除,只能删除数据. 执行命令: curl -H "Content-Type: application/json" -X ...

  9. 一个后端开发者的前端语言基础:JavaScript

    JavaScript (一) 基本概述 (1) 概述 JavaScript一种直译式脚本语言,是一种动态类型.弱类型.基于原型的语言,内置支持类型.它的解释器被称为JavaScript引擎,为浏览器的 ...

  10. 用户字符串操作,这里面包括字符串的decode、encode、substract等等操作

    工具类描述:用户字符串操作,这里面包括字符串的decode.encode.substract等等操作 package cn.hgnulb; import java.io.UnsupportedEnco ...