Attributes

NUnit 1.0使用传统的基于继承和命名约定来识别测试。从2.0开始NUnit使用自定义特性来实现。

因为NUnittest fixtures不是从框架类库继承,所以开发人员可以用其他方式轻松的使用继承。由于没有任何命名约定,故名称的选择可以是完全面向通信测试目标。

所有NUnit特性都包含在NUnit.Framework命名空间。每个包含测试的源文件都必须声明nunit.framework.dll程序集命名空间的引用。

从NUnit2.4.6开始,NUnit特性不再是密封的,任何继承这些特性的特性都能够被NUnit识别。

Action Attributes (NUnit 2.6)

NUnit设计Action特性用来更好的操作测试逻辑的组合性功能。在编写单元测试时我们希望在测试周期中运行特定的事件的逻辑需求(e.g. SetUp, TearDown, FixtureSetUp, FixtureTearDown, etc.)。NUnit通过使用适当的特性可以执行这些事件。Action特性允许用户创建自定义特性来封装用于 before or after测试用例的特别操作。

The Problem of Composability

假设我们在多个fixture中都包含测试用例,并且这些需要创建或者销毁相同的内存测试数据。我们可以创建一个fixture基类并且从它进行派生具体类。或者我们在fixture命名空间下创建一个SetUpFixture类。

This works fine, until we need some other reusable functionality, say the ability to configure or reset a ServiceLocator. (翻译不出来)

我们可以将这个功能放到fixture基类或者setup fixture中,但是我们要向基类混合两种不同的责任。在使用setup fixture时,在通用命名空间只有在所有类都请求两个特征才会有效。某些情况下我们可能不希望测试数据库,但是我们需要ServiceLocator配置,有时又正好相反,有时又同时需要,所以我们必须使得基类是可配置的。

如果我们发现需要像配置线程CurrentPrincipal一样使用任意方法来重用第三方功能可以快速解决问题。我们违反了单一原则并体会到了劣势。我们期望的是分离重用的测试逻辑,然后组合为我们需要的测试。

Resolving the Problem

Action特性可以让我们摆脱绑定,看下面的例子

View Code
[TestFixture, ResetServiceLocator]
public class MyTests
{
 [Test, CreateTestDatabase]
 public void Test1() { /* ... */ }

 [Test, CreateTestDatabase, AsAdministratorPrincipal]
 public void Test2() { /* ... */ }

 [Test, CreateTestDatabase, AsNamedPrincipal("charlie.poole")]
 public void Test3() { /* ... */ }

 [Test, AsGuestPrincipal]
 public void Test4() { /* ... */ }
}

我们使用了用户定义的特性来识别5个我们想要用不同的方式组合为不同的测试的不同操作:

  • ResetServiceLocator
  • CreateTestDatabase
  • AsAdministratorPrincipal
  • AsNamedPrincipal
  • AsGuestPrincipal

我们在其他test fixtures中通过声明合适的特性来重用。而不用从一个基类来继承。

Implementing an Action Attribute

Action特性需要程序猿来定义。通过ITestAction接口来实现,结果定义如下:

View Code
  public interface ITestAction
    {
        void BeforeTest(TestDetails details);

        void AfterTest(TestDetails details);

        ActionTargets Targets { get; }
    }

为了方便,你可以从NUnit'sTestActionAttribute派生出来,这个抽象类虚拟实现了接口的每个成员。另外,你也可以从 System.Attribute派生出来并直接实现接口。

Action Targets

当调用BeforeTest and  Targets属性返回的确定值 。ActionTargets美剧定义如下

View Code
   [Flags]
    public enum ActionTargets
    {
        Default = 0,

        Test = 1,

        Suite = 2
    }

当一个特性返回ActionTargets.Suite则意味着应用到一个类或者一个参数化的方法,NUnit会优先执行特性的BeforeTest方法,返回指向测试用例,然后在用例执行结束后执行AfterTest方法。这和TestFixtureSetUp andTestFixtureTearDown特性工作方式相似。

另一方面,在相同情况下使用返回ActionTargets.Test 的特性。NUnit在执行测试是会优先执行BeforeTest方法,然后执行测试用例,最后执行AfterTest方法,这个执行方式与SetUp and TearDown特性的工作方式类似。

返回ActionTargets.Default 的操作会附加到特定的代码中。当附加到一个方法时体现的就像已经指定了ActionTargets.Test 。当附加到一个类或者程序集是,体现为好像已经返回 ActionTargets.Suite值。

Test Details

 BeforeTest and AfterTest方法为将要运行的或者已经运行的测试提供某些信息。TestDetails类提供了如下的私有属性, before or after方法可以使用这些属性来决定执行哪些操作:

  • Fixture - an object representing the user fixture, if available, or null
  • Method - the MethodInfo that implements the test, if available, or null
  • FullName - a string giving the full name of the test
  • Type - a string representing the type of the test, e.g. "Test Case"
  • IsSuite - true if the test is a suite, otherwise false

Examples

下面的示例使用相同的操作特性:

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class |
                AttributeTargets.Interface | AttributeTargets.Assembly,
                AllowMultiple = true)]
public class ConsoleActionAttribute : Attribute, ITestAction
{
    private string _Message;

    public ConsoleActionAttribute(string message) { _Message = message; }

    public void BeforeTest(TestDetails details)
    {
        WriteToConsole("Before", details);
    }

    public void AfterTest(TestDetails details)
    {
        WriteToConsole("After", details);
    }

    public ActionTargets Targets
    {
        get { return ActionTargets.Test | ActionTargets.Suite; }
    }

    private void WriteToConsole(string eventMessage, TestDetails details)
    {
        Console.WriteLine("{0} {1}: {2}, from {3}.{4}.",
            eventMessage,
            details.IsSuite ? "Suite" : "Case",
            _Message,
            fixture != null ? fixture.GetType().Name : "{no fixture}",
            method != null ? method.Name : "{no method}");
    }
}
View Code

相关文章:

  • 2021-11-24
  • 2021-05-21
  • 2021-06-24
  • 2021-11-07
  • 2021-06-22
  • 2021-06-16
  • 2021-09-05
  • 2021-07-21
猜你喜欢
  • 2021-10-10
  • 2021-07-11
  • 2021-11-18
  • 2021-12-19
  • 2021-09-22
  • 2022-01-09
  • 2021-08-18
相关资源
相似解决方案