簡體   English   中英

以接口為參數的void方法的單元測試

[英]Unit test for void method with Interface as parameter

單元測試的新手,我有下面的示例代碼,我想為此創建一個單元測試,請建議我應該怎么做才能為此創建一個單元測試? 任何鏈接或指針都有助於開始

public class UserNotification : Work
{
    public override void Execute(IWorkContext iwc)
    {
        throw new InvalidWorkException($"some message:{iwc.Name} and :{iwc.Dept}");
    }
}

編輯:使用 MSTest 進行單元測試

因為您的標題特別提到您正在嘗試測試具有void返回類型的方法; 我推斷您已經用實際返回值測試了方法,因此您已經有了一個測試項目並且知道如何在編寫測試后運行測試。 如果不; Mithgroth 寫的答案很好地解釋了如何開始一般測試。


您的測試由您希望測試的行為定義。 您的代碼段沒有任何行為,因此很難給您具體的答案。

我選擇重寫你的例子:

public class UserNotification : Work
{
    public override void Execute(IWorkContext iwc)
    {
        var splines = iwc.GetSplines();

        iwc.Reticulate(splines);
    }
}

現在我們有一些我們想要測試的行為。 測試目標是回答以下問題:

調用Execute時, UserNotification獲取所需的樣條並將它們網狀化?

進行單元測試時,您想模擬所有其他東西 在這種情況下, IWorkContext是一個外部依賴項,因此應該對其進行模擬。 Mocking 工作上下文使我們能夠輕松配置模擬以幫助進行測試。 當我們運行測試時,我們將傳遞一個充當間諜的IWorkContext object。 本質上,這個模擬的 object 將:

  • ...已設置為返回一組非常具體的樣條,我們為測試目的選擇的樣條。
  • ...秘密記錄對Reticulate方法的任何調用,並跟蹤傳遞給它的參數。

在深入了解如何模擬之前,我們已經可以概述我們的測試將如何進行到 go:

[Test]
public void ReticulatesTheContextSplines()
{
    // Arrange
    IWorkContext mockedContext = ...; // This comes later
    UserNotification userNotification = new UserNotification();

    // Act
    userNotification.Execute(mockedContext);

    // Assert
    // Confirm that Reticulate() was called
    // Confirm that Reticulate() was given the result from `GetSplines()`
     
}

這是你的基本單元測試。 剩下的就是創建我們的模擬。

如果你願意,你可以自己寫這個。 只需創建一個實現IWorkContext的新 class ,並為其提供更多公共屬性/方法以幫助您跟蹤事物。 一個非常簡單的例子是:

public class MockedWorkContext : IWorkContext
{
    // Allows the test to set the returned result
    public IEnumerable<Spline> Splines { get; set; }

    // History of arguments used for calls made to Reticulate. 
    // Each call will add an entry to the list.
    public List<IEnumerable<Spline>> ReticulateArguments { get; private set; } = new List<IEnumerable<Spline>>();

    public IEnumerable<Spline> GetSplines()
    {
        // Returns the preset splines that the test configured
        return this.Splines;
    }

    // Mocked implementation of Reticulate()
    public void Reticulate(IEnumerable<Spline> splines)
    {
        // Does nothing except record what you passed into it
        this.ReticulateArguments.Add(splines);
    }
}

這是一個非常簡化的實現,但它完成了工作。 測試現在看起來像這樣:

[Test]
public void ReticulatesTheContextSplines()
{
    // Arrange
    IEnumerable<Spline> splines = new List<Spline>() { new Spline(), new Spline() }; // Just create some items here, it's random test data.
    IWorkContext mockedContext = new MockedWorkContext();
    mockedContext.Splines = splines;

    UserNotification userNotification = new UserNotification();

    // Act
    userNotification.Execute(mockedContext);

    // Assert - Confirm that Reticulate() was called
    mockedContext.ReticulateArguments.Should().HaveCount(1);

    // Confirm that Reticulate() was given the result from `GetSplines()`
    mockedContext.ReticulateArguments[0].Should().BeEquivalentTo(splines);
     
}

該測試現在可以准確地測試您的方法的行為。 它使用模擬上下文作為間諜來報告您的被測單元(即UserNotification )對您傳遞給的上下文所做的事情。

請注意,我在這里使用的是 FluentAssertions,因為我發現它是最易讀的語法。 隨意使用您自己的斷言邏輯。

雖然您可以編寫自己的模擬; 有 mocking 個庫可以幫助減少樣板。 據我所知,Moq 和 NSubstitute 是最受歡迎的兩個。 我個人更喜歡 NSubstitute 的語法; 但兩者都同樣出色地完成了工作。

首先,您需要一個測試項目以及您的常規項目。 你可以從這三個中選擇:

  • MS測試
  • n單位
  • x單位

所有這些都應該在VS2022中有一個項目模板。

xUnit 是一個流行的,所以讓我們選擇它。 測試項目的通常命名約定是YourProject.Tests UnitTest1.cs class 重命名為UserNotificationTests.cs

就這么簡單,您現在可以開始編寫測試了。 在 xUnit 中,具有[Fact]屬性的方法是測試方法。

using Xunit;

namespace MyProject.Tests
{
    public class UserNotificationTests
    {
        [Fact]
        public void Execute_Should_Throw_InvalidWorkException_With_Message()
        {

        }
    }
}

不要把這些方法當成代碼中的方法,命名要接近英文句子,要像正則句一樣能顯露意圖。

單元測試的經典方法分為三個階段:

  • 安排:獲取對象的實例,設置預期的 output,模擬依賴項,讓它們准備就緒。
  • Act:調用你要測試的實際動作。
  • 斷言:檢查您的實際 output 是否與您預期的 output 相關。

讓我們從安排開始吧。

  • 我們需要一個新的 UserNotification 實例UserNotification以便我們可以調用Execute()
  • 我們需要任何虛擬IWorkContext object 以便我們可以傳遞它。 為此,我們將使用NSubstitute 庫
// Don't forget to add using NSubstitute

// Arrange
var userNotification = new UserNotification();
var workContext = Substitute.For<IWorkContext>();
workContext.Name = "testName";
workContext.Dept = "testDept";

現在你行動,並調用你的方法:

// Act
Action act = () => userNotification.Execute(workContext);

最后我們斷言。 我強烈推薦用於斷言的FluentAssertations 庫

// Assert
act.Should().Throw<InvalidWorkException>()
.WithMessage($"some message:{workContext.Name} and :{workContext.Dept}");

導航到 View > Test Explorer 並運行您的測試,您應該會看到類似於此的內容:

在此處輸入圖像描述

恭喜,您編寫了第一個單元測試。 這是測試代碼的最終版本:

using FluentAssertions;
using NSubstitute;
using System;
using Xunit;

namespace MyProject.Tests
{
    public class UserNotificationTests
    {
        [Fact]
        public void Execute_Should_Throw_InvalidWorkException_With_Message()
        {
            // Arrange
            var userNotification = new UserNotification();
            var workContext = Substitute.For<IWorkContext>();
            workContext.Name = "testName";
            workContext.Dept = "testDept";

            // Act
            Action act = () => userNotification.Execute(workContext);

            // Assert
            act.Should().Throw<InvalidWorkException>()
                .WithMessage($"some message:{workContext.Name} and :{workContext.Dept}");
        }
    }

    public class UserNotification : Work
    {
        public override void Execute(IWorkContext iwc)
        {
            throw new InvalidWorkException($"some message:{iwc.Name} and :{iwc.Dept}");
        }
    }

    public abstract class Work
    {
        public virtual void Execute(IWorkContext iwc) { }
    }

    public interface IWorkContext 
    {
        public string Name { get; set; }
        public string Dept { get; set; }
    }

    public class InvalidWorkException : System.Exception
    {
        public InvalidWorkException() { }
        public InvalidWorkException(string message) : base(message) { }
        public InvalidWorkException(string message, System.Exception inner) : base(message, inner) { }
        protected InvalidWorkException(
          System.Runtime.Serialization.SerializationInfo info,
          System.Runtime.Serialization.StreamingContext context) : base(info, context) { }
    }
}

編寫測試感覺與編寫常規代碼有很大不同。 但隨着時間的推移,你會掌握它的竅門。 如何模擬、如何行動、如何斷言,這些可能因您測試的內容而異。 要點是隔離你想要單元測試的主要東西,並模擬rest。

祝你好運!

如果你想使用 nunit,帶有示例的文檔很容易理解,鏈接如下。

Nunit 文檔

而且我認為所有其他單元測試框架都與此類似。

[Test]
public void Execute_WhenCalled_ThrowArgumentException()
{
    //Initialize an instance of IWorkContext 
    var iwc = new WorkContext();
    //or use a Mock object, later on in assert use
    //userNotification.Execute(iwc.Object) 
    var iwc = new Mock<IWorkContext>();

    var userNotification = new UserNotification();

    Assert.Throws(typeof(InvalidWorkException), () =>
    {     
        userNotification.Execute(iwc) 
    });
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM