4

電話番号の領域を制御するために、次の単純なクラスデータ注釈があります。

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;
    }
}

このクラスの正しい単体テストがどのようになるかわかりません。

ありがとう。

4

2 に答える 2

3

さて、基本的に属性をテストすることは、通常のクラスをテストすることと同じです。私はあなたのクラスを取り、それを実行できるように少し減らしました(あなたは私が再作成したくないいくつかの拡張メソッドを作成しました)。以下に、このクラス定義を示します。

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;
    }
}

事前に注意してください:単体テストの命名規則のいくつかは、使用するものとは異なる場合があります(いくつかあります)。

次に、ユニットテストを作成します。すでにテストプロジェクトがあることを理解しました。テストプロジェクトがない場合は、作成するだけです。このテストプロジェクトでは、新しい単体テスト(基本単体テスト)を作成します。名前を付けましょうPhoneAreaAttributeTest

良い習慣として、テストイニシャライザーを作成して、すべての共有「リソース」(この場合はPhoneAreaAttributeクラスの新しいインスタンス)を作成します。はい、「通常の」クラスで慣れているように、インスタンスを作成するだけで済みます(実際、「通常の」クラスと属性クラスの間にそれほど大きな違いはありません)。

これで、メソッドのテストを書き始める準備ができました。基本的に、考えられるすべてのシナリオを処理する必要があります。ここでは、私の(縮小された)IsValidメソッドで可能な2つのシナリオを示します。最初に、指定されたオブジェクトパラメータを文字列にケース化できるかどうかを確認します(これは最初のシナリオ/ TestMethodです)。次に、「IsNullOrEmpty」のパスが適切に処理されているかどうかを確認します(これは2番目のシナリオ/ 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.");
    }
}
于 2012-05-04T17:29:54.463 に答える
1

このクラスを「正しく」テストする方法を検討するときは、次のことを考慮してください。

  • 循環的複雑度(CC)IsValidは5です。
  • IsNullOrWhiteSpaceこの方法は、他の2つの方法とに依存していLengthBetweenます。これらの両方に2の追加CCがあると思います。
  • を投げる可能性がありInvalidCastExceptionます。これは、別の潜在的なテストケースを表しています。

合計で、テストが必要なケースが8つある可能性があります。xUnit.netとFluentAssertions *( 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を使用するのが好きです。この場合、アサーションが失敗したときに通知するメッセージを指定できるので役立ちます。これは、失敗したアサーションです。これらのデータ駆動型テストは、さまざまな順列をグループ化することで、作成する必要のある同様のテストメソッドの数を減らすことができるという点で優れています。これを行うときは、説明されているように、カスタムメッセージによるアサーションルーレットを避けることをお勧めします。ちなみに、FluentAssertionsは多くのテストフレームワークで機能します。

于 2012-05-04T17:30:26.677 に答える