使用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, );
}
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, );
}
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, );
}
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>
在里面某个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 = ;
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 creation
System.Threading.Thread.Sleep();
}
}
}
在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);
}
}
}
看一下上面的代码, 里面有一个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实例:
这一部分先到这, 还剩下最后一部分了.
使用xUnit为.net core程序进行单元测试(3)的更多相关文章
- 使用xUnit为.net core程序进行单元测试(上)
一. 导读 为什么要编写自动化测试程序(Automated Tests)? 可以频繁的进行测试 可以在任何时间进行测试,也可以按计划定时进行,例如:可以在半夜进行自动测试. 肯定比人工测试要快. 可以 ...
- 使用xUnit为.net core程序进行单元测试(1)
导读 为什么要编写自动化测试程序(Automated Tests)? 可以频繁的进行测试 可以在任何时间进行测试,也可以按计划定时进行,例如:可以在半夜进行自动测试. 肯定比人工测试要快. 可以更快速 ...
- 使用xUnit为.net core程序进行单元测试(中)
第一部分: http://www.cnblogs.com/cgzl/p/8283610.html 下面有一点点内容是重叠的.... String Assert 测试string是否相等: [Fact] ...
- 使用xUnit为.net core程序进行单元测试(4)
第1部分: http://www.cnblogs.com/cgzl/p/8283610.html 第2部分: http://www.cnblogs.com/cgzl/p/8287588.html 第3 ...
- 使用xUnit为.net core程序进行单元测试 -- Assert
第一部分: http://www.cnblogs.com/cgzl/p/8283610.html Assert Assert做什么?Assert基于代码的返回值.对象的最终状态.事件是否发生等情况来评 ...
- 使用xUnit为.net core程序进行单元测试(2)
第一部分: http://www.cnblogs.com/cgzl/p/8283610.html 下面有一点点内容是重叠的.... String Assert 测试string是否相等: [Fact] ...
- 使用xUnit为.net core程序进行单元测试
第1部分: http://www.cnblogs.com/cgzl/p/8283610.html 第2部分: http://www.cnblogs.com/cgzl/p/8287588.html ...
- 好代码是管出来的——.Net Core中的单元测试与代码覆盖率
测试对于软件来说,是保证其质量的一个重要过程,而测试又分为很多种,单元测试.集成测试.系统测试.压力测试等等,不同的测试的测试粒度和测试目标也不同,如单元测试关注每一行代码,集成测试关注的是多个模块是 ...
- .NET Core: 在.NET Core中进行单元测试
单元测试能够帮助开发人员确保所开发的模块.类以及类中的方法等的正确性,在项目开发过程中,及时进行单元测试能够避免不必要的BUG以及提高测试效率. 在本文中,我们会分别来学习如何使用MSTest.xUn ...
随机推荐
- bzoj 1935: [Shoi2007]Tree 园丁的烦恼
Description 很久很久以前,在遥远的大陆上有一个美丽的国家.统治着这个美丽国家的国王是一个园艺爱好者,在他的皇家花园里种植着各种奇花异草.有一天国王漫步在花园里,若有所思,他问一个园丁道: ...
- hihoCoder #1078 : 线段树的区间修改(线段树区间更新板子题)
#1078 : 线段树的区间修改 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 对于小Ho表现出的对线段树的理解,小Hi表示挺满意的,但是满意就够了么?于是小Hi将问题 ...
- [bzoj3673] 可持久化并查集 by zky
总感觉到现在才来写这题有点奇怪. 并查集如果按秩合并的话,每次合并只会修改一个点的父亲. 用可持久化线段树来实现可持久化数组就行了.. 然而我写的是按子树大小合并..结果比按秩合并慢了一点>_& ...
- SecureCRT连接虚拟机中的Linux系统(Ubuntu)_Linux教程
有道云笔记链接地址: https://note.youdao.com/share/?id=826781e7ca1fd1223f6a43f4dc2c9b5d&type=note#/
- [UWP]使用Reveal
1. 前言 之前在 如何使用Fluent Design System 这篇文章里已经简单介绍过Reveal的用法,这篇再详细介绍其它内容. 2. 自定义RevealButtonStyle 我觉得常用I ...
- html日历(3)
<!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content ...
- insertBefore方法(javascript与jQuery)
说到insertBefore()方法,其实javascript与jQuery中都有此方法,那么他们用法是否相同呢? 其实,还是有点区别的.反正我是爱搞混淆了,先做个小笔记吧! 1.insertBefo ...
- sha1() 函数
sha1() 函数计算字符串的 SHA-1 散列. sha1() 函数使用美国 Secure Hash 算法 1. 来自 RFC 3174 的解释 - 美国 Secure Hash 算法 1:SHA- ...
- SSL和SSH有什么区别
SSL 是一种安全协议,它为网络(例如因特网)的通信提供私密性.SSL 使应用程序在通信时不用担心被窃听和篡改. SSL 实际上 是共同工作的两个协议:"SSL 记录协议"(SSL ...
- OpenGL进行简单的通用计算实例
博主作为OpenGL新手,最近要用OpenGL进行并行的数据计算,突然发现这样的资料还是很少的,大部分资料和参考书都是讲用OpenGL进行渲染的.好不容易找到一本书<GPGPU编程技术,从Ope ...