依赖属性之“风云再起”二

前端之家收集整理的这篇文章主要介绍了依赖属性之“风云再起”二前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

五. 引入测试驱动开发

1,引入概念

  由于本篇的依赖属性体系是基于测试驱动开发完成的,所以我们就先来看一下什么叫测试驱动开发:测试驱动开发的基本思想就是在开发功能代码之前, 先编写测试代码。也就是说在明确要开发某个功能后,首先思考如何对这个功能进行测试,并完成测试代码的编写,然后编写相关的代码满足这些测试用例。然后循 环进行添加其他功能,直到完全部功能的开发。由于过程很长,在写的时候也省略了不少步骤,所以有些地方衔接不是那么的流畅,对此表示非常的抱歉!

2,注意事项

根据自身做项目使用TDD的一点微薄经验,总结了以下几个注意事项:
◆ 找准切入点:
  不论是开发一个新的系统还是复原系统,都必须先找准一个或多个切入点,从切入点经历”测试代码功能代码-测试-重构“来逐渐完善整个系统,往往这个切入点就是功能点,就是这个系统具备哪些功能,然后根据这些功能写出测试用例。
◆ 测试列表:
  大家都知道一个系统或者一个框架都是很庞大的,如果要引入测试驱动开发,首先我们必须要有一个测试列表,在任何阶段想添加功能需求问题时,把相 关功能点加到测试列表中,然后继续开发的工作。然后不断的完成对应的测试用例、功能代码、重构。这样可以避免疏漏的同时也能把控当前的进度。
◆ 测试驱动:
  这个比较核心。完成某个功能,某个类,首先编写测试代码,考虑其如何使用、如何测试。然后在对其进行设计、编码。这里也强调先编写对功能代码的判断用的断言语句,然后编写相应的辅助语句。
◆ 良好的代码设计及可测性:
功能代码设计、开发时应该具有较强的可测试性。应该尽量保持良好的设计原则和代码规范,如尽量依赖于接口、尽量高内聚、低耦合等等。
◆ 模块或功能隔离:
  不同代码的测试应该相互隔离。对一块代码的测试只考虑此代码的测试,不要考虑其实现细节,不然就会陷入一团乱麻之中,这个可以通过MOCK来实现,同时在开始的时候也要划分好边界。
◆ 适当引入MOCK:
  在适当情况下引入MOCK来完成单元测试,这种情况尤其是在边际交互比较多的案例当中,对于交互比较多且复杂的多个类关系可以用MOCK暂时模拟,这是一个不错的解决方案。
◆ 由小到大、由偏到全、统筹兼顾:
  一个产品或者一个项目是比较大的,所以我们这里就需要遵循由小到大、由偏到全、统筹兼顾的原则,分解功能代码。把所有的规模大、复杂性高的工作,分解成小的任务来完成,这样既方便团队协作,同时也减轻了复杂度,使整个开发一下子变得简单了许多。
◆ 保持随时重构的习惯
  很多开发者在经过测试代码功能代码-测试通过以后就当完成了任务,其实你会发现随着其他功能的引入或者使用过程中发现了很多重复、冗余的代 码、再或者先前的代码结构和设计不太合理,这个时候就需要随时的进行重构和单元测试,在一方面可以避免产生风险,另一方面可以使系统更加完善。
◆ 随时进行回归:
  在”测试代码功能代码-测试-重构“的循环中一定要记住多回归,因为这样可以保证当前的代码是不是会影响到前面的功能,其实只需要看看红绿灯就行。
查看和统计代码覆盖率
  通过前面的步骤之后,我们就要看一下实现的功能是否达到我们的预期目标,除了功能完善之外,还要保证代码的覆盖率,因为它是一个系统稳定与否、可维护性与否的一个重大标志。

3,工具介入

  以后写关于TDD的文章可能比较多,同时也都会用到这个工具,所以我们今天对它也稍带介绍一下,正所谓“工欲善其事,必先利其器”。根据官方文 档解释:TestDriven.NET是Visual Studio的一个TDD插件,最近版本是TestDriven.NET-3.0.2749 RTM版。其中一些新特性有:支持MSTest、.NET Reflector 6 Pro、VS 2010、Silverlight 4、NUnit 2.5.3,使用项目所用的.NET框架等。 下载地址: http://www.testdriven.net/
这个工具使用起来比VS自带的单元测试和测试覆盖功能好用,所以从2008年开始基本就用它作为一个必备的工具使用。关于它具体的功能和怎么使用,我们这里不详细介绍,网上也有很多文章,大家可以做一下参考和研究。下图是安装后以插件的形式出现在VS中的效果

2010-9-23

A,基本介绍

  TestDriven.NET原来叫做NUnitAddIn,它是个Visual Studio插件,集成了如下测试框架:NUnit、MbUnit、 ZaneBug、MSTest、NCover、NCoverExplorer、Reflector、TypeMock、dotTrace和MSBee,它 主要面向使用TDD的开发者,主要特性列举如下:
单键运行方法、类、命名空间、项目和解决方案中的单元测试
能够快速测试实例方法、静态方法属性
可以直接跳到.NET Reflector中的任何方法、类型、项目或引用中,这个功能提供了相当大的方便
在调试过程中可以查看.NET Reflector中的任何模块或堆栈信息
支持多种单元测试框架,包括NUnit、MbUnit、xUnit和MSTest
测试运行在自己的进程中以消除其他问题和边际效应
可以轻松对任何目标测试进行调试或执行代码覆盖率测试(比微软自带的单元测试和代码覆盖功能要好用多了)
支持所有主流的.NET语言:C#、VB、C++和F#

B,TestDriven.NET 3.0中的新特性:

TestDriven.Net是基于.NET框架的。再由于VS 2010支持使用多个.NET版本,所以支持各个VS版本和工具就没有问题了
完全支持在VS 2008和VS 2010中使用MSTest
完全支持.NET Reflector 6 Pro
支持NUnit 2.5.3
支持和兼容VS 2005、VS 2008、VS 2010几个版本
支持Silverlight 4的测试

C,兼容性

  TestDriven.NET兼容于如下VS版本:Windows XP、Vista、Windows 7、Windows 2000、Windows 2003和Windows 2008(32和64位)上的Visual Studio 2005、2008和2010。官方已经不再对VS 2003支持

D,版本

企业版:每台机器一个许可认证
专业版:一般的许可形式
个人版:面向学生、开源开发者和试验用户的免费许可(大家可以下载这个版本,个人感觉很好用)

4,关于本篇

  本篇文章没有明确的写作意图,只是最近在深入研究MONO源码时有感而发,当然作者本人也只是起到了一个研究者或者剖析者的角色。首先实现最简 单且基本的DependencyProperty.Register功能,然后再实现DependencyObject的GetValue和 SetValue,接着实现PropertyMetadata的DefaultValue、PropertyChangedCallback、 CoerceValueCallback等功能,然后完善DependencyProperty.Register注册添加 ValidateValueCallback、RegisterAttached、RegisterAttachedReadOnly、 RegisterReadOnly、OverrideMetadata、GetMetadata和AddOwner等相关功能。既然有了这些功能,自然就 需要完善PropertyMetadata的IsSealed、Merge和OnApply等相关底层操作。当然在中间还需要 DependencyObject的ClearValue、CoerceValue、GetLocalValueEnumerator、 ReadLocalValue以及其他的Helper类,这里就不一一进行说明。对于边际交互比较多且关联比较大的操作,采用了Mock进行暂时模拟,在 开发完了以后再进行了替换。在开发过程中,随时进行单元测试和覆盖率的检查,这样可以方便查看哪些功能还有问题以及整体的进度和质量的监控。

六. DependencyProperty测试代码

在写DependencyProperty测试代码之前,我们先看一下它到底有哪些成员和方法,如下图:

2010-9-26

  了解了上面DependencyProperty的基本功能,我们首先创建一个继承自DependencyObject的类 ObjectPoker,由于DependencyObject还没有被创建,所以我们这里就先创建它,然后在ObjectPoker类里面实现我们的经 典语句DependencyProperty.Register,由于Register有很多重载,为了方便TDD,就从最简单的开始(三个参数,不牵涉 到元数据类),然后再创建一个ObjectPoker的子类,这是方便后面测试DependencyProperty的相关功能
   1: class ObjectPoker : DependencyObject
   2: {
   3:     //注册依赖属性property1
   4:     public static readonly DependencyProperty TestProp1 = DependencyProperty.Register("property1",typeof(string),255);">typeof(ObjectPoker));
   5: }
   6: 
   7: class SubclassPoker : ObjectPoker
   8: {
   9: }
  经过上面的测试用例通过以后,自然DependencyProperty.Register的基本功能也就完善了,然后我们来测试一下 Register两个相同的依赖属性有什么反应,由于我们为了实现Register时没有考虑那么多,所以测试是先会失败,然后在引入键值对的形式来存储 DependencyProperty,然后每个DependencyProperty都用Name.GetHashCode() ^ PropertyType.GetHashCode() ^ OwnerType.GetHashCode()来区别唯一,所以相同下面的测试用例也将完成。
1: [Test]
   2: [ExpectedException(typeof(ArgumentException))] 
   3: void TestMultipleRegisters()
   4: {
   5:     //测试注册相同名的依赖属性
   6:     DependencyProperty.Register("p1",255);">typeof(ObjectPoker));
   7:     DependencyProperty.Register(typeof(ObjectPoker));
   8: }
  我们说到依赖属性系统,其实依赖属性要依附于DependencyObject才能成为真正的依赖属性系统。所以我们来测试一下 AddOwner,每一个Owner都有自己的元数据,这个时候我们需要完善OverrideMetadata方法,然而 OverrideMetadata方法需要用到PropertyMetadata类作为参数,同时需要调用PropertyMetadata类的 DoMerge方法,我们可以创建该类,然后结合Mock完成该操作。
void TestMultipleAddOwner()
//测试AddOwner,添加相同类型的Owner
   6:     ObjectPoker.TestProp1.AddOwner(typeof(SubclassPoker),255);">new PropertyMetadata());
   7:     ObjectPoker.TestProp1.AddOwner(new PropertyMetadata());
   8: }
通过上面的测试用例以后,其实PropertyMetadata的原型已经具备了,然后我们要做的就是测试DependencyProperty的默认元数据和默认元数据的默认值。
2: void TestDefaultMetadata()
   3: {
//测试默认元数据
   5:     DependencyProperty p;
   6:     p = DependencyProperty.Register("TestDefaultMetadata1",96);">   7:     Assert.IsNotNull(p.DefaultMetadata);
   8: 
   9:     //测试元数据的默认值
  10:     p = DependencyProperty.Register("TestDefaultMetadata2",255);">typeof(ObjectPoker),255);">new PropertyMetadata("hi"));
  11:     Assert.IsNotNull(p.DefaultMetadata);
  12:     Assert.AreEqual("hi",p.DefaultMetadata.DefaultValue);
  13: }
  我们都知道一个DependencyProperty可以拥有多个Owner,每个Owner之间的区别就是用PropertyMetadata,那么这里就给该DependencyProperty添加一个Owner,然后通过该Owner来获取元数据。
void TestAddOwnerNullMetadata()
//首先注册一个依赖属性,然后再AddOwner,最后根据新的Owner获取元数据
   5:     DependencyProperty p = DependencyProperty.Register("TestAddOwnerNullMetadata",255);">typeof(ObjectPoker));
   6:     p.AddOwner(null);
   7: 
   8:     PropertyMetadata pm = p.GetMetadata(typeof(SubclassPoker));
   9:     Assert.IsNotNull(pm);
  10: }
  通过上面的测试用例,我们牵涉到了OverrideMetadata方法,当然上面没有进行实现,这个时候我们可以来实现 OverrideMetadata这个方法,首先注册一个ObjectPoker类型的依赖属性,然后通过SubclassPoker来 OverrideMetadata。
//首先注册一个依赖属性,然后再OverrideMetadata
   2: [Test]
   3: [ExpectedException(typeof(ArgumentNullException))]
   4: void TestOverrideMetadatanullMetadata()
   5: {
   6:     //有Type但PropertyMetadata为null时,OverrideMetadata操作
   7:     DependencyProperty p = DependencyProperty.Register("TestOverrideMetadatanullMetadata",96);">   8:     p.OverrideMetadata(null);
   9: }
  上面实现了OverrideMetadata的函数,但是只是简单实现,这里我们可以传入一个null类型的Type作为测试,当然测试不会通过,然后就修改代码直到测试通过吧!
typeof(ArgumentNullException))]
void TestOverrideMetadatanullType()
//当Type为null,OverrideMetadata操作
   6:     DependencyProperty p = DependencyProperty.Register("TestOverrideMetadatanullType",96);">   7:     p.OverrideMetadata(null,96);">   8: }
  如果仔细分析DependencyProperty的源码,你会发现有一个DependencyPropertyKey类,这个类到底是干嘛的 呢?其实这个类的主要作用就是构造函数传入该DependencyProperty,然后通过Type来OverrideMetadata,这里只是提供 了一个简单的封装,如果没有这个类,其他功能照样正常。
typeof(InvalidOperationException))]
void TestReadonlyOverrideMetadata()
//通过DependencyPropertyKey的方式OverrideMetadata
   6:     DependencyPropertyKey ro_key = DependencyProperty.RegisterReadOnly("readonly-prop1",
   7:                                         double),
   8:                                            9:                                         double.NaN));
  10:     ro_key.DependencyProperty.OverrideMetadata(new PropertyMetadataPoker());
  11: }
最后我们来测试一样通过DependencyPropertyKey类来注册一个ReadOnly的依赖属性,然后进行OverrideMetadata,基本和上一个测试用例类似。
void TestReadonlyOverrideMetadataFromKey()
//通过DependencyPropertyKey的方式OverrideMetadata
   5:     DependencyPropertyKey ro_key = DependencyProperty.RegisterReadOnly("readonly-prop2",
   6:                                         double.NaN));
   9:     ro_key.OverrideMetadata(new PropertyMetadataPoker());
  10: }
  通过上面的测试用例,DependencyProperty类已经基本完成,除了该类,其他诸如DependencyObject、 PropertyMetadata、DependencyPropertyKey也已经初步完成,所以我们这里先以DependencyProperty 作为切入点,那么下面就来看一下刚才创建的DependencyProperty类。

七. DependencyProperty实现代码

具体代码如下,我们就不做过多阐述,不过有几点需要注意:
1,一个依赖属性可能有多个所有者,所以根据每个所有者都有自己的元数据。
2,依赖属性私有构造函数,作为初始化操作,每个依赖属性注册的时候都会调用并初始化数据
3,为了区别不同的依赖属性,Name、PropertyType、OwnerType的哈希值取异。
4,注册依赖属性有以下几个种类:Register、RegisterAttached、RegisterAttachedReadOnly和RegisterReadOnly,所以要区别对待。
5,由于一个依赖属性可能有多个Owner,根据每个Owner都有自己的元数据,所以要有根据Owner的AddOwner、GetMetadata和OverrideMetadata的操作。
1:
using System.Collections.Generic;
namespace System.Windows 
sealed class DependencyProperty
   6:     {
   7:         //一个依赖属性可能有多个所有者,所以根据每个所有者都有自己的元数据
   8:         private Dictionary<Type,PropertyMetadata> MetadataByType = new Dictionary<Type,PropertyMetadata>();
   9: 
  10:         //声明一个UnsetValue
  11:         readonly object UnsetValue = new object ();
  12: 
  13:         //依赖属性私有构造函数,作为初始化操作,每个依赖属性注册的时候都会调用并初始化数据
  14:         private DependencyProperty (bool isAttached,255);">string name,Type propertyType,Type ownerType,
  15:                         PropertyMetadata defaultMetadata,
  16:                         ValidateValueCallback validateValueCallback)
  17:         {
  18:             IsAttached = isAttached;
  19:             DefaultMetadata = (defaultMetadata == null ? new PropertyMetadata() : defaultMetadata);
  20:             Name = name;
  21:             OwnerType = ownerType;
  22:             PropertyType = propertyType;
  23:             ValidateValueCallback = validateValueCallback;
  24:         }
  25: 
  26:         internal bool IsAttached { get; set; }
  27:         bool ReadOnly { get; private set; }
  28:         public PropertyMetadata DefaultMetadata { get; private set; }
  29:         string Name { get; private set; }
  30:         public Type OwnerType { get; private set; }
  31:         public Type PropertyType { get; private set; }
  32:         public ValidateValueCallback ValidateValueCallback { get; private set; }
  33: 
  34:         //获取依赖属性的编号,暂未实现,在上一篇“WPF基础到企业应用系列7――深入剖析依赖属性”有实现,原理是在初始化的时候++
  35:         int GlobalIndex {            
  36:             get { throw new NotImplementedException (); }
  37:         }
  38: 
  39:         //传入ownerType增加Owner
  40:         public DependencyProperty AddOwner(Type ownerType)
  41:         {
  42:             return AddOwner (ownerType,255);">null);
  43:         }
  44: 
  45:         //增加所有者,根据ownerType和typeMetadata
  46:         public DependencyProperty AddOwner(Type ownerType,PropertyMetadata typeMetadata)
  47:         {
  48:             if (typeMetadata == null) typeMetadata = new PropertyMetadata ();
  49:             OverrideMetadata (ownerType,typeMetadata);
  50: 
  51:             // MS seems to always return the same DependencyProperty
  52:             return this;
  53:         }
  54: 
  55:         //获取元数据,依据forType
  56:         public PropertyMetadata GetMetadata(Type forType)
  57:         {
  58:             if (MetadataByType.ContainsKey (forType))
  59:                 return MetadataByType[forType];
  60:             null;
  61:         }
  62: 
  63:         //获取元数据,依据该依赖属性
  64:         public PropertyMetadata GetMetadata(DependencyObject d)
  65:         {
  66:             if (MetadataByType.ContainsKey (d.GetType()))
  67:                 return MetadataByType[d.GetType()];
  68:             null;
  69:         }
  70: 
  71:         //获取元数据,依据dependencyObjectType
  72:         public PropertyMetadata GetMetadata(DependencyObjectType dependencyObjectType)
  73:         {
  74:             if (MetadataByType.ContainsKey (dependencyObjectType.SystemType))
  75:                 return MetadataByType[dependencyObjectType.SystemType];
  76:             null;
  77:         }
  78: 
  79:         //验证类型是否有效
  80:         bool IsValidType(object value)
  81:         {
  82:             return PropertyType.IsInstanceOfType (value);
  83:         }
  84: 
  85:         //验证值是否有效
  86:         bool IsValidValue(value)
  87:         {
  88:             if (!IsValidType (value))
  89:                 false;
  90:             if (ValidateValueCallback == null)
  91:                 true;
  92:             return ValidateValueCallback (value);
  93:         }
  94: 
  95:         //重写元数据,使用PropertyMetadata类的DoMerge方法来操作
  96:         void OverrideMetadata(Type forType,PropertyMetadata typeMetadata)
  97:         {
  98:             if (forType == null)
  99:                 new ArgumentNullException ("forType");
 100:             null)
 101:                 "typeMetadata");
 102: 
 103:             if (ReadOnly)
 104:                 new InvalidOperationException (String.Format ("Cannot override Metadata on readonly property '{0}' without using a DependencyPropertyKey",Name));
 105: 
 106:             typeMetadata.DoMerge (DefaultMetadata,255);">this,forType);
 107:             MetadataByType.Add (forType,typeMetadata);
 108:         }
 109: 
 110:         //重写元数据,使用PropertyMetadata类的DoMerge方法来操作
 111:         void OverrideMetadata (Type forType,PropertyMetadata typeMetadata,DependencyPropertyKey key)
 112:         {
 113:             null)
 114:                 "forType");
 115:             null)
 116:                 "typeMetadata");
 117: 
 118:             typeMetadata.DoMerge (DefaultMetadata,forType);
 119:             MetadataByType.Add (forType,typeMetadata);
 120:         }
 121: 
 122:         override string ToString ()
 123:         {
 124:             return Name;
 125:         }
 126: 
 127:         //得到哈希值,区别不同的依赖属性,Name、PropertyType、OwnerType的哈希值取异
 128:         int GetHashCode ()
 129:         {
 130:             return Name.GetHashCode() ^ PropertyType.GetHashCode() ^ OwnerType.GetHashCode();
@H_862_1301@ 131:         }
 132: 
 133:         //注册依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type)
 134:         static DependencyProperty Register( 135:         {
 136:             return Register(name,propertyType,ownerType,255);">null);
 137:         }
 138: 
 139:         //注册依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type、元数据)
 140:          141:                               PropertyMetadata typeMetadata)
 142:         {
 143:             null);
 144:         }
 145: 
 146:         //注册依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type、元数据、验证回调委托)
 147:          148:                               PropertyMetadata typeMetadata,
 149:                               ValidateValueCallback validateValueCallback)
 150:         {
 151:             null)
 152:                 typeMetadata = new PropertyMetadata();
 153: 
 154:             DependencyProperty dp = new DependencyProperty(false,name,
 155:                                        typeMetadata,validateValueCallback);
 156:             DependencyObject.register(ownerType,dp);
 157: 
 158:             dp.OverrideMetadata (ownerType,typeMetadata);
 159: 
 160:             return dp;
 161:         }
 162: 
 163:         //注册附加依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type)
 164:         static DependencyProperty RegisterAttached( 165:         {
 166:             return RegisterAttached(name,255);">null);
 167:         }
 168: 
 169:         //注册附加依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type、元数据)
 170:          171:                                   PropertyMetadata defaultMetadata)
 172:         {
 173:             null);
 174:         }
 175: 
 176:         //注册附加依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type、元数据、验证回调委托)
 177:          178:                                   PropertyMetadata defaultMetadata,
 179:                                   ValidateValueCallback validateValueCallback)
 180:         {
 181:             DependencyProperty dp = true,
 182:                                        defaultMetadata,validateValueCallback);
 183:             DependencyObject.register(ownerType,dp);
 184:             return dp;
 185:         }
 186: 
 187:         //注册只读依赖属性,暂未实现
 188:         static DependencyPropertyKey RegisterAttachedReadOnly( 189:                                          PropertyMetadata defaultMetadata)
 190:         {
 191:             new NotImplementedException("RegisterAttachedReadOnly(string name,PropertyMetadata defaultMetadata)");
 192:         }
 193: 
 194:         //注册只读依赖属性,暂未实现
 195:          196:                                          PropertyMetadata defaultMetadata,
 197:                                          ValidateValueCallback validateValueCallback)
 198:         {
 199:             Metadata defaultMetadata,ValidateValueCallback validateValueCallback)");
 200:         }
 201: 
 202:         //注册只读依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type、元数据)
 203:         static DependencyPropertyKey RegisterReadOnly( 204:                                      PropertyMetadata typeMetadata)
 205:         {
 206:             return RegisterReadOnly (name,255);">null);
 207:         }
 208: 
 209:         //注册只读依赖属性(参数:依赖属性名、依赖属性的Type、拥有者的Type、元数据、验证回调委托)
 210:          211:                                      PropertyMetadata typeMetadata,
 212:                                      ValidateValueCallback validateValueCallback)
 213:         {
 214:             DependencyProperty prop = Register (name,validateValueCallback);
 215:             prop.ReadOnly = true;
 216:             new DependencyPropertyKey (prop);
 217:         }
 218: 
 219:     }
 220: }
通过前面的步骤,DependencyProperty已经完成,那么下面我们再来看一下DependencyObject类。

猜你在找的设计模式相关文章