ZetCode

C# FakeItEasy

上次修改时间:2023 年 9 月 2 日

在本文中,我们将展示如何使用 FakeItEasy 库在 C# 中进行模拟。

模拟 (Faking) 是用伪造对象 (fakes) 或测试替身 (test doubles) 替换外部依赖项。 这些类或组件在单元测试中模拟成功或失败的操作。

使用伪造对象主要是因为外部依赖项当前可能无法用于测试,或者它们的使用成本非常高。

FakeItEasy 是一个易于使用的 .NET 模拟库。

$ dotnet add package FakeItEasy
$ dotnet add package Microsoft.NET.Test.Sdk
$ dotnet add package MSTest.TestAdapter
$ dotnet add package MSTest.TestFramework

在本文中,我们使用 MSTest 测试框架。

C# FakeItEasy 简单示例

在第一个示例中,我们用伪造对象替换一个简单的 HelloService 类。

Services/IMessageService.cs
namespace Messages.Services;

public interface IMessageService
{
    string GetHelloMessage();
    string GetGreetingMessage();
}

FakeItEasy 从 IMessageService 接口创建伪造对象。

Services/MessageService.cs
namespace Messages.Services;

public class MessageService : IMessageService
{
    public string GetHelloMessage()
    {
        return "Hello there!";
    }

    public string GetGreetingMessage()
    {
        return "Good Morning!";
    }
}

MessageServiceIMessageService 的实现。

tests/MessageServiceTest.cs
namespace Messages.Tests;

using Microsoft.VisualStudio.TestTools.UnitTesting;
using FakeItEasy;
using Messages.Services;

[TestClass]
public class MessageServiceTest
{
    private const string Expected1 = "Hello there!";
    private const string Expected2 = "Good Morning!";

    [TestMethod]
    public void HelloMessageTest()
    {
        var msgService = A.Fake<IMessageService>();
        A.CallTo(() => msgService.GetHelloMessage()).Returns(Expected1);

        var res = msgService.GetHelloMessage();
        Assert.AreEqual(Expected1, res);
    }

    [TestMethod]
    public void GreetingMessageTest()
    {
        var msgService = A.Fake<IMessageService>();
        A.CallTo(() => msgService.GetGreetingMessage()).Returns(Expected2);

        var res = msgService.GetGreetingMessage();
        Assert.AreEqual(Expected2, res);
    }
}

MessageServiceTest 中,我们测试 MessageService 类。

var msgService = A.Fake<IMessageService>();
A.CallTo(() => msgService.GetHelloMessage()).Returns(Expected1);

我们从 IMessageService 接口创建一个伪造的 MessageService,并为 GetHelloMessage 调用定义一个响应。

var res = msgService.GetHelloMessage();
Assert.AreEqual(Expected1, res);

该方法使用伪造对象而不是实际类进行测试。

C# FakeItEasy 示例 II

由于我们有一个带有测试框架的控制台程序,我们需要将以下选项添加到项目文件中。

<GenerateProgramFile>false</GenerateProgramFile>

否则,我们将有两个主要的入口点发生冲突。

$ dotnet add package Bogus

除了前面提到的包之外,我们还添加了 Bogus 库来创建伪造数据。

Models/User.cs
namespace Users.Models;

public class User
{
    public User(string fname, string lname, string occupation) =>
        (FirstName, LastName, Occupation) = (fname, lname, occupation);

    public string FirstName { get; set; }
    public string LastName { get; set; }
    public string Occupation { get; set; }

    public override string ToString() => $"{FirstName} {LastName} is a {Occupation}";
}

这是 User 类。

Services/IUserService.cs
namespace Users.Services;

using Users.Models;

public interface IUserService
{
    User GetUser();
    IList<User> GetUsers(int n);
}

我们有一个 IUserService 接口,其中包含两个合约方法。 GetUser 返回单个用户,GetUsers 返回 n 个用户。

Services/UserService.cs
namespace Users.Services;

using Users.Models;
using Bogus;

public class UserService : IUserService
{
    private List<string> occupations = new List<string> { "teacher",
        "programmer", "driver", "accountant" };

    public IList<User> GetUsers(int n)
    {
        var users = new List<User>();

        foreach (int value in Enumerable.Range(1, n))
        {
            users.Add(CreateUser());
        }

        return users;
    }

    public User GetUser()
    {
        var user = CreateUser();
        return user;
    }

    public User CreateUser()
    {
        var faker = new Faker();

        var fname = faker.Person.FirstName;
        var lname = faker.Person.LastName;

        int n = occupations.Count();
        int millis = DateTime.Now.Millisecond;
        var occupation = occupations.ElementAt(new Random(millis).Next(n));

        return new User(fname, lname, occupation);
    }
}

我们的 UserService 实现了合约方法。 它使用 Bogus 为用户生成伪造数据。

Program.cs
namespace Main;

using Users.Services;

public class Program
{
    public static void Main(string[] args)
    {
        var userService = new UserService();

        var u1 = userService.GetUser();
        Console.WriteLine(u1);

        var users = userService.GetUsers(5);
        Console.WriteLine(string.Join("\n", users));
    }
}

这是使用 userService 的主控制台程序。

tests/UserServiceTest.cs
namespace UserService.Tests;

using Microsoft.VisualStudio.TestTools.UnitTesting;
using FakeItEasy;
using Users.Services;
using Users.Models;

[TestClass]
public class MessageTest
{
    [TestMethod]
    public void GetUserTest()
    {
        var userService = A.Fake<IUserService>();

        var dummyUser = A.Dummy<User>();
        A.CallTo(() => userService.GetUser()).Returns(dummyUser);

        var res = userService.GetUser();
        Assert.AreEqual(dummyUser, res);
    }

    [TestMethod]
    [DataRow(2)]
    [DataRow(5)]
    [DataRow(10)]
    public void GetUsersTest(int n)
    {
        var userService = A.Fake<IUserService>();
        var dummyUsers = A.CollectionOfFake<User>(n);

        A.CallTo(() => userService.GetUsers(n)).Returns(dummyUsers);

        var res = userService.GetUsers(n);
        Assert.AreEqual(dummyUsers, res);
    }
}

我们使用测试替身测试 UserService 类。

var userService = A.Fake<IUserService>();

首先,我们创建一个伪造的 UserService

var dummyUser = A.Dummy<User>();
A.CallTo(() => userService.GetUser()).Returns(dummyUser);

然后我们定义一个虚拟用户,并将其设置为 GetUser 方法调用的响应。

var res = userService.GetUser();
Assert.AreEqual(dummyUser, res);

最后,我们使用测试替身测试 GetUser 方法。

[TestMethod]
[DataRow(2)]
[DataRow(5)]
[DataRow(10)]
public void GetUsersTest(int n)
{
    var userService = A.Fake<IUserService>();
    var dummyUsers = A.CollectionOfFake<User>(n);

    A.CallTo(() => userService.GetUsers(n)).Returns(dummyUsers);

    var res = userService.GetUsers(n);
    Assert.AreEqual(dummyUsers, res);
}

使用 MSTest 的 DataRow,我们依次使用值 2、5 和 10 运行 GetUsers 方法。

来源

FakeItEasy 文档

在本文中,我们已经用 C# 中的 FakeItEasy 将两个实际类替换为测试替身。

作者

我叫 Jan Bodnar,是一位充满激情的程序员,拥有丰富的编程经验。 我从 2007 年开始撰写编程文章。到目前为止,我已经撰写了超过 1,400 篇文章和 8 本电子书。 我拥有超过十年的编程教学经验。

列出所有 C# 教程