[UnitTest] - 用FluentAssertions让单元测试代码更优美

Posted by William Basics on Sunday, October 17, 2021

Introduction

早在2005年,Martin Fowler就提出了“fluent interface“这种编码风格(link)。我们比较熟悉的LINQ就是这”fluent“风格的。

linq

这种编码风格,使得代码表达十分流畅,更容易阅读和理解。FluentAssertions正是应用了”fluent"的编码风格的断言工具。

FluentAssertions的license是Apache License 2.0(link)。它支持.NET Framework 4.7+以及.NET Core 2.1+,以及MSTest2、xUnit、NUnit等测试框架。

下面我们来看下,FluentAssertions能如何帮到我们。

Let’s coding

把FluentAssertions加到我们的工程中十分简单,只需要通过NuGet安装。

nuget

官网给的第一个例子就十分体现出”fluent“的含义。

using FluentAssertions;
using Xunit;

namespace Comparison
{
    public class UnitTest
    {
        [Fact]
        public void Test_Simple_Example()
        {
            string actual = "ABCDEFGHI";
            actual.Should().StartWith("AB").And.EndWith("HI").And.Contain("EF").And.HaveLength(9);
        }
    }
}

下面是不用FluentAssertions的同一个测试代码

using Xunit;

namespace Comparison
{
    public class UnitTest
    {
        [Fact]
        public void Test_Simple_Example()
        {
            string actual = "ABCDEFGHI";

            Assert.StartsWith("AB", actual);
            Assert.EndsWith("HI", actual);
            Assert.Contains("EF", actual);
            Assert.Equal(10, actual.Length);
        }
    }
}

简洁和表意程度,前后对比,一目了然。

并且你可以将多个断言打包在一个AssertionScope里

[Fact]
public void Test_AssertScope() {
    using (new AssertionScope()) {
        5.Should().Be(10);
        "Actual".Should().Be("Expected");
    }
}

这样的话,scope里的所有失败都会被打印出来。

Xunit.Sdk.XunitException
Expected value to be 10, but found 5.
Expected string to be "Expected" with a length of 8, but "Actual" has a length of 6, differs near "Act" (index 0).

   at FluentAssertions.Execution.XUnit2TestFramework.Throw(String message)
   at FluentAssertions.Execution.TestFrameworkProvider.Throw(String message)
   at ...

一些很有特点的用法

运行时间

Action someAction = () => Thread.Sleep(100);
someAction.ExecutionTime().Should().BeLessThanOrEqualTo(200.Milliseconds());

对于Task

Func<Task> someAsyncWork = () => SomethingReturningATask();
await someAsyncWork.Should().CompleteWithinAsync(100.Milliseconds());

// 包括对返回值的检查
Func<Task<int>> someAsyncFunc;
await someAsyncFunc.Should().CompleteWithinAsync(100.Milliseconds()).WithResult(42);

浮点数精度

float value = 3.1415927F;
value.Should().BeApproximately(3.14F, 0.01F);

GUID

Guid theGuid = Guid.NewGuid();
Guid sameGuid = theGuid;
Guid otherGuid = Guid.NewGuid();

theGuid.Should().Be(sameGuid);
theGuid.Should().NotBe(otherGuid);

还有其他很多有特点的断言,大家可以去看官网文档

以上是对FluentAssertions的一个简单介绍,希望能够帮助大家写出更好的单元测试。

(end.)