一、本篇简单介绍下在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. idea使用maven私服

    nexus3中央仓库改为阿里云/ 参考:这里写链接内容  找到中央仓库 然后修改成:  http://maven.aliyun.com/nexus/content/groups/public/   1 ...

  2. netty 聊天室

    https://blog.csdn.net/qq_37372007/article/details/82937584 使用netty实现一个多人聊天室--failed: Error during We ...

  3. patch工具的使用

    1. 最简用法 patch -p1 < jello.patch

  4. C# 使用Task执行异步操作

    为什么要使用 Task Task 和 Thread 区别 Task 介绍 Task 简单实现 Task 执行状态 为什么要使用 Task 线程是创建并发的底层工具,因此具有一定的局限性. 没有简单的方 ...

  5. 解读typescript中 super关键字的用法

    解读typescript中 super关键字的用法 传统的js,使用prototype实现父.子类继承.如果父.子类有同名的方法,子类去调用父类的同名方法需要用 “父类.prototype.metho ...

  6. AndroidStudio制作Nine-Patch【.9】图片

    使用AndroidStudio制作Nine-Patch[.9]图片,以及为什么要制作Nine-Patch[.9]图片[以聊天气泡为例]   本文链接:https://blog.csdn.net/She ...

  7. nginx限制流量

    location /download/ { limit_rate_after 5m; limit_rate 1m; alias /data/html/; }

  8. 0.9.0.RELEASE版本的spring cloud alibaba nacos实例

    简而言之,nacos与eureka的不同之处有三:后台老板.部署方式.功能.nacos是阿里的,eureka是奈飞的:nacos有自己的安装包,需要独立部署,eureka仅作为一个服务组件,引入jar ...

  9. mysql无法导入函数和存储过程解决方法

    1. mysql> SET GLOBAL log_bin_trust_function_creators = 1; 2. 系统启动时 --log-bin-trust-function-creat ...

  10. Jsoup-简单爬取知乎推荐页面(附:get_agent())

    总览 今天我们就来小用一下Jsoup,从一个整体的角度来看一看爬虫 一个基本的爬虫框架包括: [x] 解析网页 [x] 失败重试 [x] 抓取内容保存至本地 [x] 多线程抓取 *** 分模块讲解 将 ...