使用xUnit为.net core程序进行单元测试(3)

第1部分: http://www.cnblogs.com/cgzl/p/8283610.html

第2部分: http://www.cnblogs.com/cgzl/p/8287588.html

请使用这个项目作为练习的开始: https://pan.baidu.com/s/1ggcGkGb

测试的分组

打开Game.Tests里面的BossEnemyShould.cs, 为HaveCorrectPower方法添加一个Trait属性标签:

        [Fact][Trait("Category", "Enemy")]public void HaveCorrectPower(){BossEnemy sut = new BossEnemy();Assert.Equal(166.667, sut.SpecialAttackPower, 3);}

Trait接受两个参数, 作为测试分类的Name和Value对.

Build项目, Run All Tests, 然后选择选择一下按Traits分组:

这时, Test Explorer里面的tests将会这样显示:

再打开EnemyFactoryShould.cs, 为CreateNormalEnemyByDefault方法添加Trait属性标签:

        [Fact][Trait("Category", "Enemy")]public void CreateNormalEnemyByDefault(){EnemyFactory sut = new EnemyFactory();Enemy enemy = sut.Create("Zombie");Assert.IsType<NormalEnemy>(enemy);}

Build, 然后查看Test Explorer:

不同的Category:

修改一下BossEnemyShould.cs里面的HaveCorrectPower方法的Trait属性:

        [Fact][Trait("Category", "Boss")]public void HaveCorrectPower(){BossEnemy sut = new BossEnemy();Assert.Equal(166.667, sut.SpecialAttackPower, 3);}

Build之后, 将会看见两个分类:

在Class级别进行分类:

只需要把Trait属性标签移到Class上面即可:

    [Trait("Category", "Enemy")]public class EnemyFactoryShould{

Build, 查看Test Explorer可以发现EnemyFactoryShould下面所有的Test方法都分类到了Enemy下:

按分类运行测试

鼠标右键点击分类, Run Selected Tests就会运行该分类下所有的测试:

按Trait搜索:

在Test Explorer中把分类选择到Class:

然后在旁边的Search输入框中输入关键字, 这时下方会有提示菜单:

点击Trait, 然后如下图输入, 就会把Enemy分类的测试过滤显示出来:

这种方式同样也可以进行Trait过滤.

使用命令行进行分类测试

使用命令行进入的Game.Tests, 首先执行命令dotnet test, 这里显示一共有27个tests:

然后, 可以使用命令: 

dotnet test --filter Category=Enemy

运行分类为Enemy的tests, 结果如图, 有8个tests:

运行多个分类的tests:

dotnet test --filter "Category=Boss|Category=Enemy"

这句命令会运行分类为Boss或者Enemy的tests, 结果如图:

共有9个tests.

忽略Test

为Fact属性标签设置其Skip属性, 即可忽略该测试, Skip的值为忽略的原因:

        [Fact(Skip = "不需要跑这个测试")]public void CreateNormalEnemyByDefault_NotTypeExample(){EnemyFactory sut = new EnemyFactory();Enemy enemy = sut.Create("Zombie");Assert.IsNotType<DateTime>(enemy);}

Build, 查看Test Explorer, 选择按Trait分类显示, 然后选中Category[Enemy]运行选中的tests:

从这里可以看到, 上面Skip的test被忽略了.

回到命令行, 执行dotnet test:

也可以看到该测试被忽略了, 并且标明了忽略的原因.

打印自定义测试输出信息:

在test中打印信息需要用到ITestOutputHelper的实现类(注意: 这里使用Console.Writeline是无效的), 在BossEnemyShould.cs里面注入这个helper:

using Xunit;
using Xunit.Abstractions;namespace Game.Tests
{public class BossEnemyShould{private readonly ITestOutputHelper _output;public BossEnemyShould(ITestOutputHelper output){_output = output;}
......

然后在test方法里面这样写即可:

        [Fact][Trait("Category", "Boss")]public void HaveCorrectPower(){_output.WriteLine("正在创建 Boss Enemy");BossEnemy sut = new BossEnemy();Assert.Equal(166.667, sut.SpecialAttackPower, 3);}

Build, Run Tests, 这时查看测试结果会发现一个output链接:

点击这个链接, 就会显示测试的输出信息:

使用命令行:

dotnet test --filter Category=Boss --logger:trx

执行命令后:

可以看到生成了一个TestResults文件夹, 里面是测试的输出文件, 使用编辑器打开, 它是一个xml文件, 内容如下:

<?xml version="1.0" encoding="UTF-8"?>
<TestRun id="9e552b73-0636-46a2-83d9-c19a5892b3ab" name="solen@DELL-RED 2018-02-10 10:27:19" runUser="DELL-RED\solen" xmlns="http://microsoft.com/schemas/VisualStudio/TeamTest/2010"><Times creation="2018-02-10T10:27:19.5005784+08:00" queuing="2018-02-10T10:27:19.5005896+08:00" start="2018-02-10T10:27:17.4990291+08:00" finish="2018-02-10T10:27:19.5176327+08:00" /><TestSettings name="default" id="610cad4c-1066-417b-a8e6-d30dce78ef4d"><Deployment runDeploymentRoot="solen_DELL-RED_2018-02-10_10_27_19" /></TestSettings><Results><UnitTestResult executionId="4c6ec739-ccd3-4233-b2bd-8bbde4dfa67f" testId="9e476ed4-3cd9-4f51-aa39-b3d411369979" testName="Game.Tests.BossEnemyShould.HaveCorrectPower" computerName="DELL-RED" duration="00:00:00.0160000" startTime="2018-02-10T10:27:19.2099922+08:00" endTime="2018-02-10T10:27:19.2113656+08:00" testType="13cdc9d9-ddb5-4fa4-a97d-d965ccfc6d4b" outcome="Passed" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" relativeResultsDirectory="4c6ec739-ccd3-4233-b2bd-8bbde4dfa67f"><Output><StdOut>正在创建 Boss Enemy</StdOut></Output></UnitTestResult></Results><TestDefinitions><UnitTest name="Game.Tests.BossEnemyShould.HaveCorrectPower" storage="c:\users\solen\projects\game\game.tests\bin\debug\netcoreapp2.0\game.tests.dll" id="9e476ed4-3cd9-4f51-aa39-b3d411369979"><Execution id="4c6ec739-ccd3-4233-b2bd-8bbde4dfa67f" /><TestMethod codeBase="C:\Users\solen\projects\Game\Game.Tests\bin\Debug\netcoreapp2.0\Game.Tests.dll" executorUriOfAdapter="executor://xunit/VsTestRunner2/netcoreapp" className="Game.Tests.BossEnemyShould" name="Game.Tests.BossEnemyShould.HaveCorrectPower" /></UnitTest></TestDefinitions><TestEntries><TestEntry testId="9e476ed4-3cd9-4f51-aa39-b3d411369979" executionId="4c6ec739-ccd3-4233-b2bd-8bbde4dfa67f" testListId="8c84fa94-04c1-424b-9868-57a2d4851a1d" /></TestEntries><TestLists><TestList name="Results Not in a List" id="8c84fa94-04c1-424b-9868-57a2d4851a1d" /><TestList name="All Loaded Results" id="19431567-8539-422a-85d7-44ee4e166bda" /></TestLists><ResultSummary outcome="Completed"><Counters total="1" executed="1" passed="1" failed="0" error="0" timeout="0" aborted="0" inconclusive="0" passedButRunAborted="0" notRunnable="0" notExecuted="0" disconnected="0" warning="0" completed="0" inProgress="0" pending="0" /><Output><StdOut>[xUnit.net 00:00:00.5525795]   Discovering: Game.Tests[xUnit.net 00:00:00.6567207]   Discovered:  Game.Tests[xUnit.net 00:00:00.6755272]   Starting:    Game.Tests[xUnit.net 00:00:00.8743059]   Finished:    Game.Tests</StdOut></Output></ResultSummary>
</TestRun>
View Code

在里面某个Output标签内可以看到上面写的测试输出信息.

减少重复的代码

xUnit在执行某个测试类的Fact或Theory方法的时候, 都会创建这个类新的实例, 所以有一些公用初始化的代码可以移动到constructor里面.

打开PlayerCharacterShould.cs, 可以看到每个test方法都执行了new PlayerCharacter()这个动作. 我们应该把这段代码移动到constructor里面:

namespace Game.Tests
{public class PlayerCharacterShould{private readonly PlayerCharacter _playerCharacter;private readonly ITestOutputHelper _output;public PlayerCharacterShould(ITestOutputHelper output){
       _output = output;
_output.WriteLine("正在创建新的玩家角色");_playerCharacter = new PlayerCharacter();
}[Fact]public void BeInexperiencedWhenNew(){Assert.True(_playerCharacter.IsNoob);}[Fact]public void CalculateFullName(){_playerCharacter.FirstName = "Sarah";_playerCharacter.LastName = "Smith";Assert.Equal("Sarah Smith", _playerCharacter.FullName);
......

Build, Run Tests, 都OK, 并且都有output输出信息.

除了集中编写初始化代码, 也可以集中编写清理代码:

这需要该测试类实现IDisposable接口:

 

public class PlayerCharacterShould: IDisposable{......public void Dispose(){_output.WriteLine($"正在清理玩家{_playerCharacter.FullName}");}
}

 

Build, Run Tests, 然后随便查看一个该类的test的output:

可以看到Dispose()被调用了.

在执行测试的时候共享上下文

上面降到了每个测试方法运行的时候都会创建该测试类新的实例, 可以在constructor里面进行公共的初始化动作.

但是如果初始化的动作消耗资源比较大, 并且时间较长, 那么这种方法就不太好了, 所以下面介绍另外一种方法.

首先在Game项目里面添加类:GameState.cs:

using System;
using System.Collections.Generic;namespace Game
{public class GameState{public static readonly int EarthquakeDamage = 25;public List<PlayerCharacter> Players { get; set; } = new List<PlayerCharacter>();public Guid Id { get; } = Guid.NewGuid();public GameState(){CreateGameWorld();}        public void Earthquake(){foreach (var player in Players){player.TakeDamage(EarthquakeDamage);}}public void Reset(){Players.Clear();}private void CreateGameWorld(){// Simulate expensive creationSystem.Threading.Thread.Sleep(2000);}}
}
View Code

在Game.Tests里面添加类: GameStateShould.cs:

using Xunit;namespace Game.Tests
{public class GameStateShould{[Fact]public void DamageAllPlayersWhenEarthquake(){var sut = new GameState();var player1 = new PlayerCharacter();var player2 = new PlayerCharacter();sut.Players.Add(player1);sut.Players.Add(player2);var expectedHealthAfterEarthquake = player1.Health - GameState.EarthquakeDamage;sut.Earthquake();Assert.Equal(expectedHealthAfterEarthquake, player1.Health);Assert.Equal(expectedHealthAfterEarthquake, player2.Health);}[Fact]public void Reset(){var sut = new GameState();var player1 = new PlayerCharacter();var player2 = new PlayerCharacter();sut.Players.Add(player1);sut.Players.Add(player2);sut.Reset();Assert.Empty(sut.Players);            }}
}
View Code

看一下上面的代码, 里面有一个Sleep 2秒的动作, 所以执行两个测试方法的话每个方法都会执行这个动作, 一共用了这些时间:

为了解决这个问题, 我们首先建立一个类 GameStateFixture.cs, 它需要实现IDisposable接口:

using System;namespace Game.Tests
{public class GameStateFixture : IDisposable{public GameState State { get; private set; }public GameStateFixture(){State = new GameState();}public void Dispose(){// Cleanup
        }}
}

然后在GameStateShould类实现IClassFixture接口并带有泛型的类型:

using Xunit;
using Xunit.Abstractions;namespace Game.Tests
{public class GameStateShould : IClassFixture<GameStateFixture>{private readonly GameStateFixture _gameStateFixture;private readonly ITestOutputHelper _output;public GameStateShould(GameStateFixture gameStateFixture, ITestOutputHelper output){_gameStateFixture = gameStateFixture;_output = output;}[Fact]public void DamageAllPlayersWhenEarthquake(){_output.WriteLine($"GameState Id={_gameStateFixture.State.Id}");var player1 = new PlayerCharacter();var player2 = new PlayerCharacter();_gameStateFixture.State.Players.Add(player1);_gameStateFixture.State.Players.Add(player2);var expectedHealthAfterEarthquake = player1.Health - GameState.EarthquakeDamage;_gameStateFixture.State.Earthquake();Assert.Equal(expectedHealthAfterEarthquake, player1.Health);Assert.Equal(expectedHealthAfterEarthquake, player2.Health);}[Fact]public void Reset(){_output.WriteLine($"GameState Id={_gameStateFixture.State.Id}");var player1 = new PlayerCharacter();var player2 = new PlayerCharacter();_gameStateFixture.State.Players.Add(player1);_gameStateFixture.State.Players.Add(player2);_gameStateFixture.State.Reset();Assert.Empty(_gameStateFixture.State.Players);            }}
}

这个注入的_gameStateFixture在运行多个tests的时候只有一个实例. 所以把消耗资源严重的动作放在GameStateFixture里面就可以保证该段代码只运行一次, 并且被所有的test所共享调用. 要注意的是, 因为上述原因, GameStateFixture里面的代码不可以有任何副作用, 也就是说可以影响其他的测试结果.

Build, Run Tests:

可以看到运行时间少了很多, 因为那段Sleep代码只需要运行一次.

再查看一下这个两个tests的output是一样的, 也就是说明确实是只生成了一个GameState实例:

在不同的测试类中共享上下文

上面讲述了如何在一个测试类中不同的测试里共享代码的方法, 而xUnit也可以让我们在不同的测试类中共享上下文.

在Tests项目里建立 GameStateCollection.cs:

using Xunit;namespace Game.Tests
{[CollectionDefinition("GameState collection")]public class GameStateCollection : ICollectionFixture<GameStateFixture> {}
}

这个类GameStateCollection需要实现ICollectionFixture<T>接口, 但是它没有具体的实现.

它上面的CollectionDefinition属性标签作用是定义了一个Collection名字叫做GameStateCollection. 

再建立TestClass1.cs:

using Xunit;
using Xunit.Abstractions;namespace Game.Tests
{[Collection("GameState collection")]public class TestClass1{private readonly GameStateFixture _gameStateFixture;private readonly ITestOutputHelper _output;public TestClass1(GameStateFixture gameStateFixture, ITestOutputHelper output){_gameStateFixture = gameStateFixture;_output = output;}[Fact]public void Test1(){_output.WriteLine($"GameState ID={_gameStateFixture.State.Id}");}[Fact]public void Test2(){_output.WriteLine($"GameState ID={_gameStateFixture.State.Id}");}}
}

和TestClass2.cs:

using Xunit;
using Xunit.Abstractions;namespace Game.Tests
{[Collection("GameState collection")]public class TestClass2{private readonly GameStateFixture _gameStateFixture;private readonly ITestOutputHelper _output;public TestClass2(GameStateFixture gameStateFixture, ITestOutputHelper output){_gameStateFixture = gameStateFixture;_output = output;}[Fact]public void Test3(){_output.WriteLine($"GameState ID={_gameStateFixture.State.Id}");}[Fact]public void Test4(){_output.WriteLine($"GameState ID={_gameStateFixture.State.Id}");}}
}

TestClass1和TestClass2在类的上面使用Collection属性标签来调用名为GameState collection的Collection. 而不需要实现任何接口.

这样, xUnit在运行测试之前会建立一个GameState实例共享与TestClass1和TestClass2.

Build, 同时运行TestClass1和TestClass2的Tests:

运行的时间为3秒多:

查看这4个test的output, 可以看到它们使用的是同一个GameState实例:

这一部分先到这, 还剩下最后一部分了.

 

下面是我的关于ASP.NET Core Web API相关技术的公众号--草根专栏:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/284170.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

war部署到tomcat

gs-rest-service-0.1.0.war复制到tomcat-9.0.0.M17\webapps\打开server.xml&#xff0c;这Host节点&#xff0c;加入<Context path"/gs" docBase"gs-rest-service-0.1.0.war" debug"0" privileged"true"/> gs相当于虚拟目录&…

C# Thread IsBackground作用

背景之前在做一个定时下载任务的时候&#xff0c;使用的是一个主线程在执行任务&#xff1b;后面需求调整了&#xff0c;需要在启用一个子线程执行优先级更高的单独通道下载。于是下意识的这么做 new Thread//创建后台线程Thread bThread new Thread(new ThreadStart(backgrou…

产品经理的分类及术语详解

一、按项目分类 1、前端型PM 一句话概述&#xff1a;制造口碑带来流量。 偏用户体验&运营&#xff0c;通过极致的产品设计&吸引眼球的产品营销策略&#xff0c;打造口碑&#xff0c;创造一款用户量巨大的产品。 【常见术语】 UCD&#xff08;User Centered Design…

Mybatis 拦截器

Mybatis定义了四种拦截器&#xff1a; Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)ParameterHandler (getParameterObject, setParameters)ResultSetHandler (handleResultSets, handleOutputParameters)StatementHandler …

1295 N皇后问题

1295 N皇后问题 时间限制: 2 s 空间限制: 128000 KB 题目等级 : 黄金 Gold 题目描述 Description在nn格的棋盘上放置彼此不受攻击的n个皇后。按照国际象棋的规则&#xff0c;皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。n后问题等价于再nn的棋盘上放置n个皇后&…

CDN的强大功能

2019独角兽企业重金招聘Python工程师标准>>> CDN&#xff0c;内容分发网络&#xff0c;除了用作网站加速外&#xff0c;还能够更好的保护网站不被攻击。防护网站不被攻击的功能成就了CDN运行中的主要责任。CDN 防护原理是其主要在于在相关节点中成功的建立动态加速机…

IDEA创建SpringBoot项目无法连接https://start.spring.io(已解决)

错误&#xff1a; 方法&#xff1a; 将&#xff1a;https://start.spring.io 更换为 ​https://start.aliyun.com

论人生自动化

就像设备一样基本都是由三部分组成&#xff0c;输入&#xff0c;处理&#xff0c;输出&#xff0c;三部分。当输出与输入两者有比较&#xff0c;自然就产生了反馈&#xff0c;正反馈或者负反馈&#xff0c;有利于输出的稳定性。有一些东西或者事情能达到闭环&#xff0c;则一切…

MySQL默认数据库简介

类似于MS SQL Server等大型数据库&#xff0c;MySQL数据库也提供了内置的数据库&#xff0c;它们是&#xff1a;INFORMATION_SCHEMAmysqltest1.information_schema其中&#xff0c;第一个数据库INFORMATION_SCHEMA提供了访问数据库元数据的方式。元数据是关于数据的数据&#x…

mysql常见监控项

1、MySQL服务运行状态 约定所有MySQL服务都必须以ip1&#xff08;内网ip&#xff09;来绑定&#xff0c;每个机器只有一个ip1&#xff0c;可以有多个端口&#xff0c;即多个MySQL Server。采集程序读取ip端口信息文件来判断server是否存在。 sockParamps aux | grep -P "m…

2005年AMC8数学竞赛中英文真题典型考题、考点分析和答案解析

今天距离2024年的AMC8美国数学竞赛举办已不足一个月了&#xff0c;赶紧利用周末的时间刷刷真题&#xff0c;查漏补缺吧&#xff01;如果您有任何关于AMC8比赛的任何问题都可以问我&#xff0c;关于题目的解析也可以交流。 今天我们来看看2005年AMC8竞赛的五道典型考题。欢迎您查…

WPF效果第一百九十三篇之登录实现

前面一直在玩耍ListBox(最爱),大周末的就适合在家吹着风扇撸着代码;今天来分享一个很简单实用的登录,来看看最终实现的效果:1、关于软件启动后焦点实现:<Style TargetType"Border"><Style.Triggers><DataTrigger Binding"{Binding IsEmptyAccoun…

IDEA中安装并使用JRebel热部署插件

文章目录 作者简介引言导航热门专栏推荐概述安装JRebel注册JRebel配置JRebel最后小结导航热门专栏推荐作者简介 作者名&#xff1a;编程界明世隐 简介&#xff1a;CSDN博客专家&#xff0c;从事软件开发多年&#xff0c;精通Java、JavaScript&#xff0c;博主也是从零开始一步步…

UWP: 实现 UWP 应用自启动

原文:UWP: 实现 UWP 应用自启动在上一篇文章中&#xff0c;我们实现了使用命令行来启动 UWP 应用&#xff0c;在这一篇文章中&#xff0c;我们会实现 UWP 应用自启用的实现&#xff0c;也即开机后或用户登陆后&#xff0c;应用自己启动。这些特性原来都是 Win32 程序所具备的&a…

选择 GCD 还是 NSTimer ?

我们常常会延迟某件任务的执行&#xff0c;或者让某件任务周期性的执行。然后也会在某些时候需要取消掉之前延迟执行的任务。 延迟操作的方案一般有三种&#xff1a; 1.NSObject的方法&#xff1a; 2.使用NSTimer的方法&#xff1a; 3.使用GCD的方法&#xff1a; 一般情况下&am…

完美解决Idea unable to access git 错误

在命令行执行 如下命令即可 git config --global --unset http.proxy git config --global --unset https.proxy

Web框架 性能评测 -- C# 的性能 和 Rust、C++并驾齐驱

自从2021年2月第20轮公布的测试以后&#xff0c;一年半后 的2022年7月19日 发布了 TechEmpower 21轮测试报告&#xff1a;Round 21 results - TechEmpower Framework Benchmarks。Techempower benchmark是包含范围最广泛的web框架性能测试&#xff0c;覆盖了比较典型的使用场景…

CF449 C. Jzzhu and Apples

1 /*2 http://codeforces.com/problemset/problem/449/C3 cf 449 C. Jzzhu and Apples4 数论素数贪心5 */6 #include <cstdio>7 #include <algorithm>8 using namespace std;9 const int Nmax100005; 10 int is_prime[Nmax]; 11 int book[Nmax]; 12 int cnt[Nmax];…

【GlobalMapper精品教程】027:路径剖面和和视线工具的使用

文章目录 一、路径剖面简介二、创建剖面图1. 加载DEM2. 创建剖面图3. 计算填挖方3. 保存剖面图一、路径剖面简介 路径剖面视线工具允许您使用加载的高程数据集沿用户指定的路径获取垂直剖面。 要定义生成3D路径剖面所遵循的路径,只需单击鼠标左键选择路径的点,然后石键单击…

QT中VideoProbe的简介和实现

一、遇到问题在Android机上使用QT进行图像处理程序设计的时候&#xff0c;遇到的一个比较明显的问题就是图片采集的问题----摄像头获得是实时的视频&#xff0c;如果我们想从中动态地截获图片&#xff0c;并且转换成Mat的格式&#xff0c;那么仅仅是静态的imagecapturee就无法完…