[翻译]NUnit---Action Attributes(八)
2013-05-15 21:45 Max蚊子 阅读(1046) 评论(0) 编辑 收藏 举报Attributes
NUnit 1.0使用传统的基于继承和命名约定来识别测试。从2.0开始NUnit使用自定义特性来实现。
因为NUnit的test 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特性可以让我们摆脱绑定,看下面的例子
[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接口来实现,结果定义如下:
public interface ITestAction { void BeforeTest(TestDetails details); void AfterTest(TestDetails details); ActionTargets Targets { get; } }
为了方便,你可以从NUnit'sTestActionAttribute派生出来,这个抽象类虚拟实现了接口的每个成员。另外,你也可以从 System.Attribute派生出来并直接实现接口。
Action Targets
当调用BeforeTest and Targets属性返回的确定值 。ActionTargets美剧定义如下
[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}"); } }
注意,上面的操作特性返回的是ActionTargets.Test and ActionTargets.Suite集合。可以这样,但可能不是正常情况。这样做了之后我们可以在多个实例中重用这个特性。这个特性的构造函数使用了一个参数:message,用于在控制台进行输出。Before and After方法都会通过WriteToConsole方法来进行输出。
Method Attached Actions
Example 1 (applied to simple test method):
[TestFixture] public class ActionAttributeSampleTests { [Test][ConsoleAction("Hello")] public void SimpleTest() { Console.WriteLine("Test ran."); } } Console Output: Before Case: Hello, from ActionAttributeSampleTests.SimpleTest. Test ran. After Case: Hello, from ActionAttributeSampleTests.SimpleTest.
Example 2 (applied action twice to test method):
[TestFixture] public class ActionAttributeSampleTests { [Test] [ConsoleAction("Hello")] [ConsoleAction("Greetings")] public void SimpleTest() { Console.WriteLine("Test run."); } } Console Output: Before Case: Greetings, from ActionAttributeSampleTests.SimpleTest. Before Case: Hello, from ActionAttributeSampleTests.SimpleTest. Test run. After Case: Hello, from ActionAttributeSampleTests.SimpleTest. After Case: Greetings, from ActionAttributeSampleTests.SimpleTest.
Remarks
允许相同的特性应用多次。注意尽管这个以稳定的.NET版本,但是特性应用的顺序是不确定的。
Example 3 (applied to a test method with test cases):
[TestFixture] public class ActionAttributeSampleTests { [Test] [ConsoleAction("Hello")] [TestCase("02")] [TestCase("01")] public void SimpleTest(string number) { Console.WriteLine("Test run {0}.", number); } } Console Output: Before Suite: Hello, from ActionAttributeSampleTests.SimpleTest. Before Case: Hello, from ActionAttributeSampleTests.SimpleTest. Test run 01. After Case: Hello, from ActionAttributeSampleTests.SimpleTest. Before Case: Hello, from ActionAttributeSampleTests.SimpleTest. Test run 02. After Case: Hello, from ActionAttributeSampleTests.SimpleTest. After Suite: Hello, from ActionAttributeSampleTests.SimpleTest.
Remarks
当一个方法应用了一个或者多个TestCase特性时,NUnit会将这个方法当作一个 test suite。你会注意到BeforeTest会在suite运行前执行一次,AfterTest会在运行后执行一次。另外,BeforeTest and AfterTest会在每个测试用例再执行一次。注意测试用例的执行顺序是不确定的。
Type Attached Actions
Example 1:
[TestFixture] [ConsoleAction("Hello")] public class ActionAttributeSampleTests { [Test] public void SimpleTestOne() { Console.WriteLine("Test One."); } [Test] public void SimpleTestTwo() { Console.WriteLine("Test Two."); } } Console Output: Before Suite: Hello, from ActionAttributeSampleTests.{no method}. Before Case: Hello, from ActionAttributeSampleTests.SimpleTestOne. Test ran. After Case: Hello, from ActionAttributeSampleTests.SimpleTestOne. Before Case: Hello, from ActionAttributeSampleTests.SimpleTestTwo. Test ran. After Case: Hello, from ActionAttributeSampleTests.SimpleTestTwo. After Suite: Hello, from ActionAttributeSampleTests.{no method}.
Remarks
在这个测试中,这个类是test suite,BeforeTest and AfterTes在这个类构造时执行一次,在每个用例再执行一次。
Example 2 (attached to interface):
[ConsoleAction("Hello")] public interface IHaveAnAction { } [TestFixture] public class ActionAttributeSampleTests : IHaveAnAction { [Test] public void SimpleTest() { Console.WriteLine("Test run."); } } Console Output: Before Suite: Hello, from ActionAttributeSampleTests.{no method}. Before Case: Hello, from ActionAttributeSampleTests.SimpleTest. Test run. After Case: Hello, from ActionAttributeSampleTests.SimpleTest. After Suite: Hello, from ActionAttributeSampleTests.{no method}.
Remarks
操作特性可以应用于接口。如果标记为TestFixture的类实现了这个接口,那么这个类会从接口继承这个操作特性,就像你在这个类本身应用了操作特性。
Example 3 (action attribute is applied to interface and attribute uses interface to provide data to tests):
[AttributeUsage(AttributeTargets.Interface)] public class InterfaceAwareActionAttribute : TestActionAttribute { private readonly string _Message; public InterfaceAwareActionAttribute(string message) { _Message = message; } public override void BeforeTest(TestDetails details) { IHaveAnAction obj = details.Fixture as IHaveAnAction; if(obj != null) obj.Message = _Message; } public override ActionTargets Target { get { return ActionTargets.Test; } } } [InterfaceAwareAction("Hello")] public interface IHaveAnAction { string Message { get; set; } } [TestFixture] public class ActionAttributeSampleTests : IHaveAnAction { [Test] public void SimpleTest() { Console.WriteLine("{0}, World!", Message); } public string Message { get; set; } }
Console Output:
Hello, World!
Remarks
这里我们看到一个新的操作特性:InterfaceAwareAction。这个特性在BeforeTest方法中使用TestDetails的Fixture属性并强制转换为IHaveAnAction接口。如果这个fixture实现了IHaveAnAction接口,那么会将这个特性在构造函数时传入的string赋值给Message属性。因为这个特性应用于接口,任何实现了这个接口的类都会将构造函数输入的sring赋值为Message属性。在操作特性为测试用例提供数据、服务时非常有用。
注意这个特性从TestActionAttribute继承。它使用默认的AfterTest,并且重写了BeforeTest and Target。
Assembly Attached Action
Example 1:
[assembly: ConsoleAction("Hello")] [TestFixture] public class ActionAttributeSampleTests { [Test] public void SimpleTest() { Console.WriteLine("Test run."); } } Console Output: Before Suite: Hello, from {no fixture}.{no method}. Before Case: Hello, from ActionAttributeSampleTests.SimpleTest. Test run. After Case: Hello, from ActionAttributeSampleTests.SimpleTest. After Suite: Hello, from {no fixture}.{no method}.
Remarks
这个示例中的ConsoleAction特性是应用于整个程序集,NUnit会将整个程序集当作一个test suite (in fact, a suite of suites)。由于ConsoleAction特性实现了 ITestSuiteAction and ITestCaseAction两个接口,NUnit会程序集运行任何测试用例之前执行BeforeTest,运行所有测试用例之后执行AfterTest。另外,在构建保障清除状态、防止某个测试用例影响其他测试用例输出的操作特性时时非常有用的。例如,你可以使用操作特性来清除静态或者缓存的数据、者服务。
哎!翻译的很痛苦,自己都觉得不伦不类的,但是为了进步为了将来还是坚持下去。
Fighting!
作者:Max蚊子
网站:feiger.cn 飞鸽博客,关注互联网、站长圈的程序员博客!
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。