簡體   English   中英

如何對自定義數據注釋執行單元測試

[英]How to do a Unit Test for a custom Data Annotation

我有以下簡單的類數據注釋來控制電話號碼的區域:

public class PhoneAreaAttribute : ValidationAttribute, IClientValidatable
{
    public const string ValidInitNumber = "0";
    public const int MinLen = 2;
    public const int MaxLen = 4;

    public override bool IsValid(object value)
    {
        var area = (string)value;
        if (string.IsNullOrWhiteSpace(area))
        {
            return true;
        }

        if (area.StartsWith(PhoneAreaAttribute.ValidInitNumber))
        {
            return false;
        }

        if (!Regex.IsMatch(area, @"^[\d]+$"))
        {
            return false;
        }

        if (!area.LengthBetween(PhoneAreaAttribute.MinLen, PhoneAreaAttribute.MaxLen))
        {
            return false;
        }

        return true;
    }

    public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
    {
        var rule = new ModelClientValidationRule
        {
            ErrorMessage = FormatErrorMessage(metadata.GetDisplayName()),
            ValidationType = "phoneArea",
        };

        yield return rule;
    }
}

我不知道這個班級的單元測試怎么樣。

謝謝。

好的,基本上測試屬性與測試任何常規類相同。 我拿了你的課並減少了一點,所以我可以運行它(你創建了一些擴展方法,我不想重新創建)。 您可以在下面找到此類定義。

public class PhoneAreaAttribute : ValidationAttribute
{
    public const string ValidInitNumber = "0";

    public override bool IsValid(object value)
    {
        var area = (string)value;

        if (string.IsNullOrEmpty(area))
        {
            return true;
        }

        if (area.StartsWith(PhoneAreaAttribute.ValidInitNumber))
        {
            return false;
        }

        return true;
    }
}

事先注意:我的一些單元測試命名約定可能與你使用的命名約定不同(有幾個)。

現在我們將創建一個單元測試。 我知道你已經有了一個測試項目 ,如果你沒有,只需創建一個。 在這個測試項目中,您將創建一個新的單元測試( Basic Unit Test ),我們將其命名為PhoneAreaAttributeTest

作為一種好的做法,我創建了一個測試初始化​​程序來創建所有共享的“資源”,在本例中是PhoneAreaAttribute類的新實例。 是的,您可以創建一個實例,就像您習慣使用“常規”類一樣(事實上,“常規”類與屬性類之間沒有太大區別)。

現在我們准備開始編寫方法的測試。 基本上你會想要處理所有可能的場景。 我將在這里向您展示我的(簡化)IsValid方法中可能出現的兩種情況。 首先,我將看看給定的對象參數是否可以被賦予一個字符串(這是第一個場景/ TestMethod)。 其次,我將看看是否正確處理了“IsNullOrEmpty”的路徑(這是第二種情況/ TestMethod)。

如您所見,它只是一個常規的單元測試。 這些只是非常基礎。 如果您仍有疑問,我還建議您閱讀一些教程。

這是PhoneAreaAttributeTest測試類:

[TestClass]
public class PhoneAreaAttributeTest
{
    public PhoneAreaAttribute PhoneAreaAttribute { get; set; }

    [TestInitialize]
    public void PhoneAreaAttributeTest_TestInitialise()
    {
        PhoneAreaAttribute = new PhoneAreaAttribute();
    }


    [TestMethod]
    [ExpectedException(typeof(InvalidCastException))]
    public void PhoneAreaAttributeTest_IsValid_ThrowsInvalidCastException()
    {
        object objectToTest = new object();
        PhoneAreaAttribute.IsValid(objectToTest);
    }


    [TestMethod]
    public void PhoneAreaAttributeTest_IsValid_NullOrEmpty_True()
    {
        string nullToTest = null;
        string emptoToTest = string.Empty;

        var nullTestResult = PhoneAreaAttribute.IsValid(nullToTest);
        var emptyTestResult = PhoneAreaAttribute.IsValid(emptoToTest);

        Assert.IsTrue(nullTestResult, "Null Test should return true.");
        Assert.IsTrue(emptyTestResult, "Empty Test should return true.");
    }
}

在考慮如何“正確”測試此類時,請考慮以下事項:

  • IsValid圈復雜度 (CC)為5。
  • 該方法依賴於另外兩種方法IsNullOrWhiteSpaceLengthBetween 我相信這兩個都有2個額外的CC。
  • 有可能拋出InvalidCastException 這代表了另一個潛在的測試用例

總的來說,您可能需要測試8個案例。 使用xUnit.netFluent Assertions *(您可以在NUnit中執行類似的操作),您可以編寫以下單元測試來“正確”測試此方法:

public class PhoneAreaAttributeTests
{
    [Theory]
    [InlineData("123", true)]
    [InlineData(" ", true)]
    [InlineData(null, true)]
    public void IsValid_WithCorrectInput_ReturnsTrue(
        object value, bool expected)
    {
        // Setup
        var phoneAreaAttribute = CreatePhoneAreaAttribute();

        // Exercise
        var actual = phoneAreaAttribute.IsValid(value);

        // Verify
        actual.Should().Be(expected, "{0} should be valid input", value);

        // Teardown            
    }

    [Theory]
    [InlineData("012", false)]
    [InlineData("A12", false)]
    [InlineData("1", false)]
    [InlineData("12345", false)]
    public void IsValid_WithIncorrectInput_ReturnsFalse(
        object value, bool expected)
    {
        // Setup
        var phoneAreaAttribute = CreatePhoneAreaAttribute();

        // Exercise
        var actual = phoneAreaAttribute.IsValid(value);

        // Verify
        actual.Should().Be(expected, "{0} should be invalid input", value);

        // Teardown      
    }

    [Fact]
    public void IsValid_UsingNonStringInput_ThrowsExcpetion()
    {
        // Setup
        var phoneAreaAttribute = CreatePhoneAreaAttribute();
        const int input = 123;

        // Exercise
        // Verify
        Assert.Throws<InvalidCastException>(
            () => phoneAreaAttribute.IsValid(input));

        // Teardown     
    }

    // Simple factory method so that if we change the
    // constructor, we don't have to change all our 
    // tests reliant on this object.
    public PhoneAreaAttribute CreatePhoneAreaAttribute()
    {
        return new PhoneAreaAttribute();
    }
}

*我喜歡使用Fluent Assertions,在這種情況下它有幫助,因為我們可以指定一條消息,讓我們知道斷言何時失敗,哪一個是失敗的斷言。 這些數據驅動的測試很好,因為它們可以通過將各種排列組合在一起來減少我們需要編寫的類似測試方法的數量。 當我們這樣做時,最好通過自定義消息來避免斷言輪盤 順便說一句,Fluent Assertions可以與許多測試框架一起使用。

暫無
暫無

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

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