.NET 帮助

Moq C#(适用于开发者的工作原理)

乔尔迪·巴尔迪亚
乔尔迪·巴尔迪亚
2023年十月29日
分享:

在软件开发领域,测试是一个不可或缺的过程。 它可以确保您的代码按照预期运行,并有助于在生产之前发现错误。 测试的一个重要方面是模拟,而在 C# 测试方面,MOQ 是开发人员武器库中的一个强大工具。 它支持 lambda 表达式。 MOQ 是 "Mock Object Framework for .NET "的简称,它简化了为单元测试创建模拟对象的过程。 在本文中,我们将深入探讨 C# 中的 MOQ。

什么是 MOQ?

MOQ - 面向 .NET 的 Mocking 框架 是一个用于 .NET 应用程序的 mocking 框架,使开发人员能够快速高效地创建 mock 对象。 模拟对象可以模拟应用程序中真实对象的行为,从而更容易隔离和测试代码的特定部分。 MOQ 简化了创建和使用这些模拟对象的过程。

MOQ 的主要功能

  • 流畅接口:MOQ提供流畅且表达清晰的API用于设置期望和验证。 这将使您的测试代码更易读、更易懂。
  • 强类型:MOQ 利用 C# 语言特性,在定义模拟和期望时提供强类型和 IntelliSense 支持。 这样可以减少测试中出现运行时错误的几率。
  • 松散模拟:MOQ 支持严格和松散的模拟。 松散模拟允许您创建响应任何方法调用的模拟对象,而严格模拟则强制要求只调用预期的方法。
  • 可验证行为:MOQ 允许您验证在模拟对象上调用的特定方法是否使用预期的参数并按照正确的顺序执行。
  • 回调和返回: 您可以定义回调,以在调用模拟方法时执行自定义代码,并为模拟方法指定返回值。

开始使用 MOQ

在本教程中,我们将探讨如何使用 MOQ(一种流行的 C# 模拟框架)来促进单元测试。 我们将通过一个示例,使用 MOQ 模拟依赖关系,创建并测试一个简单的 ATM 交易场景。

创建一个新的C

按照以下步骤创建新项目

  1. 打开Visual Studio,依次选择“文件” > “新建” > “项目...”

  2. 选择一个项目模板,配置设置,然后点击 "创建"。

    Moq C#(开发者如何使用)图1 - 在Visual Studio 2022中创建一个新的控制台应用程序

    假设您正在为自动柜员机(ATM)开发软件,并且您需要测试身份验证和取款功能。 ATM 依赖于两个接口:IHostBankIHSMModule。 我们想测试ATMCashWithdrawal类,该类代表ATM的现金取款功能。

    创建两个接口,IHostBankIHSMModule,它们表示ATM系统的依赖项。 定义相关的方法,如authenticateAmountvalidatePIN

// IHostBank.cs
public interface IHostBank
{
    bool AuthenticateAmount(string accountNumber, int amount);
}

// IHSMModule.cs
public interface IHSMModule
{
    bool ValidatePIN(string cardNumber, int pin);
}
// IHostBank.cs
public interface IHostBank
{
    bool AuthenticateAmount(string accountNumber, int amount);
}

// IHSMModule.cs
public interface IHSMModule
{
    bool ValidatePIN(string cardNumber, int pin);
}
' IHostBank.cs
Public Interface IHostBank
	Function AuthenticateAmount(ByVal accountNumber As String, ByVal amount As Integer) As Boolean
End Interface

' IHSMModule.cs
Public Interface IHSMModule
	Function ValidatePIN(ByVal cardNumber As String, ByVal pin As Integer) As Boolean
End Interface
$vbLabelText   $csharpLabel

创建ATMCashWithdrawal类,使用上述依赖项执行ATM操作。 在本课程中,您将实现一个像WithdrawAmount这样的特定方法。

// ATMCashWithdrawal.cs
public class ATMCashWithdrawal
{
    private readonly IHSMModule hsmModule;
    private readonly IHostBank hostBank;

    public ATMCashWithdrawal(IHSMModule hsmModule, IHostBank hostBank)
    {
        this.hsmModule = hsmModule;
        this.hostBank = hostBank;
    }
// non static method
    public bool WithdrawAmount(string cardNumber, int pin, int amount)
    {
        if (!hsmModule.ValidatePIN(cardNumber, pin))
        {
            return false;
        }

        if (!hostBank.AuthenticateAmount(cardNumber, amount))
        {
            return false;
        }

        // Withdraw the specified amount and perform other operations
        return true;
    }
}
// ATMCashWithdrawal.cs
public class ATMCashWithdrawal
{
    private readonly IHSMModule hsmModule;
    private readonly IHostBank hostBank;

    public ATMCashWithdrawal(IHSMModule hsmModule, IHostBank hostBank)
    {
        this.hsmModule = hsmModule;
        this.hostBank = hostBank;
    }
// non static method
    public bool WithdrawAmount(string cardNumber, int pin, int amount)
    {
        if (!hsmModule.ValidatePIN(cardNumber, pin))
        {
            return false;
        }

        if (!hostBank.AuthenticateAmount(cardNumber, amount))
        {
            return false;
        }

        // Withdraw the specified amount and perform other operations
        return true;
    }
}
' ATMCashWithdrawal.cs
Public Class ATMCashWithdrawal
	Private ReadOnly hsmModule As IHSMModule
	Private ReadOnly hostBank As IHostBank

	Public Sub New(ByVal hsmModule As IHSMModule, ByVal hostBank As IHostBank)
		Me.hsmModule = hsmModule
		Me.hostBank = hostBank
	End Sub
' non static method
	Public Function WithdrawAmount(ByVal cardNumber As String, ByVal pin As Integer, ByVal amount As Integer) As Boolean
		If Not hsmModule.ValidatePIN(cardNumber, pin) Then
			Return False
		End If

		If Not hostBank.AuthenticateAmount(cardNumber, amount) Then
			Return False
		End If

		' Withdraw the specified amount and perform other operations
		Return True
	End Function
End Class
$vbLabelText   $csharpLabel

创建单元测试项目

现在,让我们使用 MOQ 来模拟依赖项,为 ATMCashWithdrawal 类创建单元测试。

在您的解决方案中创建一个新的单元测试项目,并将其命名为ATMSystem.Tests

要在 Visual Studio 解决方案中添加 NUnit 测试项目,请按照以下步骤操作:

  1. 右键点击解决方案:在解决方案资源管理器中(通常位于右侧),右键点击解决方案名称。

  2. 添加 > 新项目:从上下文菜单中选择“添加”,然后选择“新项目...”

  3. 创建新项目:在“添加新项目”对话框中,您可以搜索“NUnit”以找到可用的NUnit模板。 选择 NUnit 测试项目,如下所示。

    Moq C#(它对开发人员的作用)图2 - 在您的解决方案中添加一个新的 NUnit 测试项目。

  4. 配置项目:根据需要配置项目设置,包括项目名称和位置。

  5. 点击确定:点击“创建”或“确定”按钮,将NUnit测试项目添加到您的解决方案中。

    现在,您的解决方案中有了一个独立的 NUnit 测试项目,您可以在其中编写和管理单元测试。 您还可以在此项目中添加您要测试的项目的参考资料,并开始编写 NUnit 测试用例。

    要开始在测试项目中使用 MOQ,您需要将 MOQ NuGet 软件包添加到解决方案中。 您可以使用 Visual Studio 中的 NuGet 软件包管理器或在软件包管理器控制台中运行以下命令来完成这项工作:

Install-package moq

该命令将安装软件包,并在项目中添加所有必需的依赖项。

使用 NUnit 和 MOQ 编写单元测试,以模拟 ATMCashWithdrawal 类的依赖项(IHostBankIHSMModule)。

using Moq;
using MOQTestProject;

namespace UnitTest
{
    public class Tests
    {
        ATMCashWithdrawal atmCash;
        [SetUp]
        public void Setup()
        {
            // Arrange
            var hsmModuleMock = new Mock<IHSMModule>();
            hsmModuleMock.Setup(h => h.ValidatePIN("123456781234", 1234)).Returns(true);

            var hostBankMock = new Mock<IHostBank>();
            hostBankMock.Setup(h => h.AuthenticateAmount("123456781234", 500)).Returns(true);
            var atmCash = new ATMCashWithdrawal(hsmModuleMock.Object, hostBankMock.Object); // Object property
        }

        [Test]
        public void WithdrawAmount_ValidTransaction_ReturnsTrue()
        {
            // Act
            bool result = atmCash.WithdrawAmount("123456781234", 1234, 500);

            // Assert
            Assert.IsTrue(result); // Verify method 
        }

        // Add more test cases for different scenarios (e.g., invalid PIN, insufficient funds, etc.)
    }
}
using Moq;
using MOQTestProject;

namespace UnitTest
{
    public class Tests
    {
        ATMCashWithdrawal atmCash;
        [SetUp]
        public void Setup()
        {
            // Arrange
            var hsmModuleMock = new Mock<IHSMModule>();
            hsmModuleMock.Setup(h => h.ValidatePIN("123456781234", 1234)).Returns(true);

            var hostBankMock = new Mock<IHostBank>();
            hostBankMock.Setup(h => h.AuthenticateAmount("123456781234", 500)).Returns(true);
            var atmCash = new ATMCashWithdrawal(hsmModuleMock.Object, hostBankMock.Object); // Object property
        }

        [Test]
        public void WithdrawAmount_ValidTransaction_ReturnsTrue()
        {
            // Act
            bool result = atmCash.WithdrawAmount("123456781234", 1234, 500);

            // Assert
            Assert.IsTrue(result); // Verify method 
        }

        // Add more test cases for different scenarios (e.g., invalid PIN, insufficient funds, etc.)
    }
}
Imports Moq
Imports MOQTestProject

Namespace UnitTest
	Public Class Tests
		Private atmCash As ATMCashWithdrawal
		<SetUp>
		Public Sub Setup()
			' Arrange
			Dim hsmModuleMock = New Mock(Of IHSMModule)()
			hsmModuleMock.Setup(Function(h) h.ValidatePIN("123456781234", 1234)).Returns(True)

			Dim hostBankMock = New Mock(Of IHostBank)()
			hostBankMock.Setup(Function(h) h.AuthenticateAmount("123456781234", 500)).Returns(True)
			Dim atmCash = New ATMCashWithdrawal(hsmModuleMock.Object, hostBankMock.Object) ' Object property
		End Sub

		<Test>
		Public Sub WithdrawAmount_ValidTransaction_ReturnsTrue()
			' Act
			Dim result As Boolean = atmCash.WithdrawAmount("123456781234", 1234, 500)

			' Assert
			Assert.IsTrue(result) ' Verify method
		End Sub

		' Add more test cases for different scenarios (e.g., invalid PIN, insufficient funds, etc.)
	End Class
End Namespace
$vbLabelText   $csharpLabel

在此测试代码中,我们使用 MOQ 为 IHSMModuleIHostBank 创建模拟对象,并在测试期间调用时指定它们的行为。

在上面的代码示例中,我们用 C# 演示了使用 MOQ 模拟对象的概念。 我们为IHSMModuleIHostBank接口创建模拟对象,在单元测试期间模拟其行为。 这使我们能够通过控制这些模拟对象的响应来隔离并彻底测试ATMCashWithdrawal类。 通过模拟,我们可以确保代码与这些依赖关系正确交互,从而使我们的测试具有针对性和可预测性,并能有效地发现所测试的特定代码单元中存在的问题。 这种做法可以提高整体可靠性和可维护性,并使代码测试更加容易。

第 3 步 运行测试

  1. 构建您的解决方案,确保所有内容都是最新的。

  2. 在 Visual Studio 中打开测试资源管理器(测试 > 测试资源管理器)。

  3. 单击测试资源管理器中的 "全部运行 "按钮,执行单元测试。

  4. 查看测试结果。 您应该看到您编写的测试(WithdrawAmount\_ValidTransaction\_ReturnsTrue)通过。

    ![Moq C#(对开发者的工作原理)图3 - 要运行测试,首先您必须构建解决方案。 构建成功后,在 Visual Studio 中打开“测试资源管理器”,然后点击“全部运行”按钮以开始执行单元测试。

    通过这种方式,我们可以隔离我们要测试的代码,并通过有效地模拟依赖关系,确保代码在各种情况下的表现符合预期。 这种做法可以提高软件的可靠性和可维护性,从而更容易在开发过程的早期发现并解决问题。

介绍IronPDF

IronPDF 文档和功能概述 是一个强大的 C# 库,允许开发人员在其应用程序中处理 PDF 文档。 它提供了广泛的功能,包括从 HTML、图像和现有 PDF 等各种来源创建、修改和转换 PDF 文件。 如果结合上一篇教程中讨论的模拟对象概念,IronPDF 将成为在单元测试中生成和操作 PDF 文档的重要工具。

IronPDF的主要功能是其HTML到PDF转换功能,确保布局和样式完整无缺。 它能将网页内容转化为 PDF 文件,是报告、发票和文档的完美选择。 该功能支持将 HTML 文件、URL 和 HTML 字符串转换为 PDF。

using IronPdf;

class Program
{
    static void Main(string[] args)
    {
        var renderer = new ChromePdfRenderer();

        // 1. Convert HTML String to PDF
        var htmlContent = "<h1>Hello, IronPDF!</h1><p>This is a PDF from an HTML string.</p>";
        var pdfFromHtmlString = renderer.RenderHtmlAsPdf(htmlContent);
        pdfFromHtmlString.SaveAs("HTMLStringToPDF.pdf");

        // 2. Convert HTML File to PDF
        var htmlFilePath = "path_to_your_html_file.html"; // Specify the path to your HTML file
        var pdfFromHtmlFile = renderer.RenderHtmlFileAsPdf(htmlFilePath);
        pdfFromHtmlFile.SaveAs("HTMLFileToPDF.pdf");

        // 3. Convert URL to PDF
        var url = "http://ironpdf.com"; // Specify the URL
        var pdfFromUrl = renderer.RenderUrlAsPdf(url);
        pdfFromUrl.SaveAs("URLToPDF.pdf");
    }
}
using IronPdf;

class Program
{
    static void Main(string[] args)
    {
        var renderer = new ChromePdfRenderer();

        // 1. Convert HTML String to PDF
        var htmlContent = "<h1>Hello, IronPDF!</h1><p>This is a PDF from an HTML string.</p>";
        var pdfFromHtmlString = renderer.RenderHtmlAsPdf(htmlContent);
        pdfFromHtmlString.SaveAs("HTMLStringToPDF.pdf");

        // 2. Convert HTML File to PDF
        var htmlFilePath = "path_to_your_html_file.html"; // Specify the path to your HTML file
        var pdfFromHtmlFile = renderer.RenderHtmlFileAsPdf(htmlFilePath);
        pdfFromHtmlFile.SaveAs("HTMLFileToPDF.pdf");

        // 3. Convert URL to PDF
        var url = "http://ironpdf.com"; // Specify the URL
        var pdfFromUrl = renderer.RenderUrlAsPdf(url);
        pdfFromUrl.SaveAs("URLToPDF.pdf");
    }
}
Imports IronPdf

Friend Class Program
	Shared Sub Main(ByVal args() As String)
		Dim renderer = New ChromePdfRenderer()

		' 1. Convert HTML String to PDF
		Dim htmlContent = "<h1>Hello, IronPDF!</h1><p>This is a PDF from an HTML string.</p>"
		Dim pdfFromHtmlString = renderer.RenderHtmlAsPdf(htmlContent)
		pdfFromHtmlString.SaveAs("HTMLStringToPDF.pdf")

		' 2. Convert HTML File to PDF
		Dim htmlFilePath = "path_to_your_html_file.html" ' Specify the path to your HTML file
		Dim pdfFromHtmlFile = renderer.RenderHtmlFileAsPdf(htmlFilePath)
		pdfFromHtmlFile.SaveAs("HTMLFileToPDF.pdf")

		' 3. Convert URL to PDF
		Dim url = "http://ironpdf.com" ' Specify the URL
		Dim pdfFromUrl = renderer.RenderUrlAsPdf(url)
		pdfFromUrl.SaveAs("URLToPDF.pdf")
	End Sub
End Class
$vbLabelText   $csharpLabel

例如,如果您有一个涉及 PDF 生成或处理的项目,您可以使用 IronPDF 创建模拟 PDF 文档,模仿真实世界的场景。 这对于测试和验证您的代码如何与 PDF 文件交互特别有用。 您可以生成具有特定内容、布局和属性的模拟 PDF,然后将其用作测试夹具,以确保您的代码能够生成所需的 PDF 输出或正确处理 PDF 相关操作。

创建生成 PDF 的模拟对象

假设您正在开发一个生成财务报告的应用程序,这些报告需要以 PDF 文档的形式保存和分发。 在这种情况下,您可能需要测试 PDF 的生成,确保内容和格式正确无误。

首先,我们需要在项目中添加 IronPDF。 在 NuGet 软件包管理器控制台中编写以下命令安装 IronPDF。

Install-Package IronPdf

该命令将为我们的项目安装和添加必要的依赖项。

以下是 IronPDF 如何融入单元测试流程:

生成模拟 PDF

您可以使用 IronPdf 创建具有特定内容和样式的模拟 PDF 文档,以模仿真实的财务报告。 这些模拟 PDF 可以作为单元测试的测试夹具,如以下代码片段所示:

public class PDFGenerator
{
    public void GenerateFinancialReport(string reportData)
    {
        var renderer = new ChromePdfRenderer();
        // Generate the report HTML
        string reportHtml = GenerateReportHtml(reportData);
        PdfDocument pdfDocument = renderer.RenderHtmlAsPdf(reportHtml);
        // Save the PDF to a file or memory stream
        pdfDocument.SaveAsPdfA("FinancialReport.pdf");
    }

    private string GenerateReportHtml(string reportData)
    {
        // Generate the report HTML based on the provided data
        // (e.g., using Razor views or any HTML templating mechanism)
        // Return the HTML as a string

        return "<h1>my Report</h1>";
    }
}
public class PDFGenerator
{
    public void GenerateFinancialReport(string reportData)
    {
        var renderer = new ChromePdfRenderer();
        // Generate the report HTML
        string reportHtml = GenerateReportHtml(reportData);
        PdfDocument pdfDocument = renderer.RenderHtmlAsPdf(reportHtml);
        // Save the PDF to a file or memory stream
        pdfDocument.SaveAsPdfA("FinancialReport.pdf");
    }

    private string GenerateReportHtml(string reportData)
    {
        // Generate the report HTML based on the provided data
        // (e.g., using Razor views or any HTML templating mechanism)
        // Return the HTML as a string

        return "<h1>my Report</h1>";
    }
}
Public Class PDFGenerator
	Public Sub GenerateFinancialReport(ByVal reportData As String)
		Dim renderer = New ChromePdfRenderer()
		' Generate the report HTML
		Dim reportHtml As String = GenerateReportHtml(reportData)
		Dim pdfDocument As PdfDocument = renderer.RenderHtmlAsPdf(reportHtml)
		' Save the PDF to a file or memory stream
		pdfDocument.SaveAsPdfA("FinancialReport.pdf")
	End Sub

	Private Function GenerateReportHtml(ByVal reportData As String) As String
		' Generate the report HTML based on the provided data
		' (e.g., using Razor views or any HTML templating mechanism)
		' Return the HTML as a string

		Return "<h1>my Report</h1>"
	End Function
End Class
$vbLabelText   $csharpLabel

使用模拟 PDF 进行单元测试

我们将编写使用 IronPDF 生成代表各种报告场景的模拟 PDF 的测试。 然后,我们将把代码生成的实际 PDF 与这些模拟 PDF 进行比较,以确保内容、格式和结构符合预期。

internal class PDFGeneratorTests
{
    [Test]
    public void GenerateFinancialReport_CreatesCorrectPDF()
    {
        // Arrange
        var mock = new PDFGenerator();
        var expectedPdf = PdfDocument.FromFile("ExpectedFinancialReport.pdf"); // Load a mock PDF

        // Act
        mock.GenerateFinancialReport("Sample report data");
        var actualPdf = PdfDocument.FromFile("FinancialReport.pdf");

        // Assert
        Assert.AreEqual(actualPdf.ExtractAllText() , expectedPdf.ExtractAllText());
    }

}
internal class PDFGeneratorTests
{
    [Test]
    public void GenerateFinancialReport_CreatesCorrectPDF()
    {
        // Arrange
        var mock = new PDFGenerator();
        var expectedPdf = PdfDocument.FromFile("ExpectedFinancialReport.pdf"); // Load a mock PDF

        // Act
        mock.GenerateFinancialReport("Sample report data");
        var actualPdf = PdfDocument.FromFile("FinancialReport.pdf");

        // Assert
        Assert.AreEqual(actualPdf.ExtractAllText() , expectedPdf.ExtractAllText());
    }

}
Friend Class PDFGeneratorTests
	<Test>
	Public Sub GenerateFinancialReport_CreatesCorrectPDF()
		' Arrange
		Dim mock = New PDFGenerator()
		Dim expectedPdf = PdfDocument.FromFile("ExpectedFinancialReport.pdf") ' Load a mock PDF

		' Act
		mock.GenerateFinancialReport("Sample report data")
		Dim actualPdf = PdfDocument.FromFile("FinancialReport.pdf")

		' Assert
		Assert.AreEqual(actualPdf.ExtractAllText(), expectedPdf.ExtractAllText())
	End Sub

End Class
$vbLabelText   $csharpLabel

在此测试代码中,我们生成一个表示预期输出的模拟PDF(expectedPdf),并与PDFGenerator生成的PDF(actualPDF)进行比较。 我们提取了两份 PDF 的内容,以验证它们是否具有相同的内容。

结论

总之,在我们的单元测试过程中使用MOQ和IronPDF,可以让我们全面验证软件应用程序的行为。 MOQ 使我们能够隔离特定的代码组件、控制依赖关系并模拟复杂的场景,从而使我们能够编写重点突出且可靠的测试。

同时,IronPDF通过便于生成和操作PDF文档来增强我们的测试能力,确保我们的PDF相关功能得到彻底检验。 通过将这些工具集成到我们的测试工具包中,我们可以自信地开发出稳健、高质量的软件,满足对功能和性能的要求。 MOQ 强大的单元测试和 IronPDF 强大的 PDF 验证相结合,大大提高了我们应用程序的整体质量和可靠性。

值得注意的是,IronPDF为其功能测试提供了免费试用。 如果您发现它符合您的需求,您可以选择购买商业许可证,这将允许您继续在项目中使用IronPDF的功能,并享有许可证版本所带来的完整优势和支持,从而确保PDF相关功能顺利集成到您的应用程序中。

乔尔迪·巴尔迪亚
乔尔迪·巴尔迪亚
软件工程师
Jordi 最擅长 Python、C# 和 C++,当他不在 Iron Software 运用技能时,他会进行游戏编程。作为产品测试、产品开发和研究的负责人之一,Jordi 为持续的产品改进增添了极大的价值。多样化的经验让他充满挑战和参与感,他说这是他在 Iron Software 工作中最喜欢的方面之一。Jordi 在佛罗里达州迈阿密长大,并在佛罗里达大学学习计算机科学和统计学。
< 前一页
Entity Framework C#(它是如何为开发人员工作的)
下一步 >
C# Web 框架(它如何为开发者工作)