使用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="[email protected] 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 = 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 creation
            System.Threading.Thread.Sleep(2000);
        }
    }
}

在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实例:

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

原文地址:https://www.cnblogs.com/cgzl/p/8438019.html

时间: 2024-10-14 05:51:20

使用xUnit为.net core程序进行单元测试(3)的相关文章

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

第1部分: http://www.cnblogs.com/cgzl/p/8283610.html 第2部分: http://www.cnblogs.com/cgzl/p/8287588.html 第3部分: http://www.cnblogs.com/cgzl/p/8438019.html 请使用这个项目的代码: https://pan.baidu.com/s/1i7d8z2H 数据驱动的测试 打开PlayerCharacterShould.cs 添加几个Fact测试方法: [Fact] pu

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

第一部分: http://www.cnblogs.com/cgzl/p/8283610.html 下面有一点点内容是重叠的.... String Assert 测试string是否相等: [Fact] public void CalculateFullName() { var p = new Patient { FirstName = "Nick", LastName = "Carter" }; Assert.Equal("Nick Carter"

好代码是管出来的——.Net Core中的单元测试与代码覆盖率

测试对于软件来说,是保证其质量的一个重要过程,而测试又分为很多种,单元测试.集成测试.系统测试.压力测试等等,不同的测试的测试粒度和测试目标也不同,如单元测试关注每一行代码,集成测试关注的是多个模块是否能正常的协同工作. 当我们在衡量代码好坏时,其中一点就是这些代码是否进行了单元测试,测试的质量.代码覆盖率怎么样?本文将从以下几个方面介绍.Net Core中的单元测试: 单元测试简介 .Net Core中的单元测试框架 使用xUnit.Net对.Net Core应用进行单元测试 创建xUnit.

.NET Core: 在.NET Core中进行单元测试

单元测试能够帮助开发人员确保所开发的模块.类以及类中的方法等的正确性,在项目开发过程中,及时进行单元测试能够避免不必要的BUG以及提高测试效率. 在本文中,我们会分别来学习如何使用MSTest.xUnit以及NUnit这些流行的.NET测试框架来对.NET Core项目进行测试. 一.项目创建 首先,创建一个需要测试的项目.在Visual Studio中创建一个.NET Core 类库项目,名为Calc,项目创建成功后,删除自带的Class1.cs,添加一个类Calculator. Calcul

跨平台部署.NET Core程序

开发环境:Win10 开发工具:Visual Studio 2015 部署环境:centos 7-x64或macOS 10.12 一.准备工作 (一)开发机器 1. 安装VS2015 .NET Core开发工具:Visual Studio 2015 Tools (Preview 2),下载地址:https://go.microsoft.com/fwlink/?LinkId=827546: 2. 安装.NET Core SDK,下载地址:https://go.microsoft.com/fwlin

将Windows系统编译的.NET Core程序发布到Ubuntu系统

在可移植方面.NET Core应用程序分为两种,Portable application(便捷,需要目标机器安装.NET Core Runtime)和Self-contained application(独立的,又名自宿主.目标机器不需要.NET Core Runtime ), 具体可参考文档:https://docs.microsoft.com/zh-cn/dotnet/articles/core/app-types 本次是将Portable App发布到Ubuntu 16.04上运行.发布方

如何在Centos里面,把.net core程序设为开机自启动

确定你的.net core程序可以在centos手动启动后,下一步,就是把这个程序做成一个服务,让它开机自自动了 1.创建脚本文件 到目录/etc/rc.d/init.d下面,创建一个myserver.sh文件 vi myserver.sh 内容如下: #!/bin/bash# chkconfig: 2345 10 30# description: testServer dotnet /home/yourapp.dll #!/bin/bash符号#!用来告诉系统它后面的参数是用来执行该文件的程序

使用VS2013进行C#程序的单元测试(转)

没有按照预期的那样做出成功的单元测试,磕磕绊绊参照了下面两篇博客大致做出来了,所以很有必要记录一下过程. http://www.cnblogs.com/duasonir/p/5299732.html(照着这个我成功的做出了单元测试) http://www.cnblogs.com/Look_Sun/p/4514732.html(这个我几乎研究了一天,但是最后还是没有做出来,最后看到上面那位同学的参考的内容和这篇一样,拜读之后也作出了半成品) 由于程序都是简单加法,而且我自己的思想并没有加入其中,项

.net core程序部署

前期将一些程序切换到了.net core,本文这里记录下windows 下.net core程序部署相关的方法.有同样需求的朋友可以参考一下,以免少走一些弯路. .net core程序部署主要工作就是在目标机器上装上.net core runtime,它可以在微软官方的下载网站上下载. 官方的图比较清晰的介绍了其运行环境,就windows的.net core程序部署而言,主要需要安装如下两个包: .net core runtime asp.net core runtime 如果部署的是.net