歲月輕品's profile蚂蚁的窝PhotosBlogLists Tools Help

歲月輕品

Occupation
Location
baby  
Photo 1 of 12

蚂蚁的窝

榜样、座右铭皆已远去,理想藏在心底,而欲望写在脸上。人生就像一个派对,只是我的请柬被寄丢了。
August 18

最近挺烦的

我想要的生活是什么样的呢??
平淡,安静,
看看书,钓钓鱼
无聊了就出去走一走
呵呵呵,如此简单的而已
可惜,连这都很难做到
这个狗日的社会
慢慢把人都逼疯了
一个疯狂的社会,娘的
July 06

丫丫要回湖北老家了

不开心,
真的舍不得小家伙走,
她现在会自己跑着玩了,
会叫爸爸妈妈了,
宝贝,爸爸爱你,爸爸真舍不得你走,
你要是不在爸爸身边,爸爸想你会想疯的。
June 26

Smart Client Software Factory的启动过程

应网友要求,结合参考实现(BankBranchWorkbench)写一篇关于 SCSF 内部工作原理的文章,需要读者有 SCSF 基础。基本概念和基本理念后面相关文章介绍。

SCSF 自动为我们建立了 Shell 项目。该项目的 ShellApplication 是SCSF 应用的入口程序,该类继承自 SmartClientApplication<TWorkItem, TShell> ,TWorkItem 是要指定的 root workitem ,TShell 是主窗体。

该类的 Main 方法通过 new ShellApplication().Run(); 启动应用。Run() 在父类 CabApplication 中实现,定义了 SCSF 的启动流程:1 public void Run()
2 {
3     RegisterUnhandledExceptionHandler();
4     Builder builder = CreateBuilder();
5     AddBuilderStrategies(builder);
6     CreateRootWorkItem(builder);

8     IVisualizer visualizer = CreateVisualizer();
9     if (visualizer != null)
10         visualizer.Initialize(rootWorkItem, builder);
11 
12     AddRequiredServices();
13     AddConfiguredServices();
14     AddServices();
15     AuthenticateUser();
16     ProcessShellAssembly();
17     rootWorkItem.BuildUp();
18     LoadModules();
19     rootWorkItem.FinishInitialization();
20 
21     rootWorkItem.Run();
22     Start();
23 
24     rootWorkItem.Dispose();
25     if (visualizer != null)
26         visualizer.Dispose();
27 }
28 

其中核心流程有:

1. 首先创建 Builder builder = CreateBuilder(); 

CreaterBuilder()方法注册了 RootWorkItemInitializationStrategy ,EventBrokerStrategy,CommandStrategy,ObjectBuiltNotificationStrategy 总共四个策略,同时还添加了三个 Policy :SingletonPolicy,BuilderTraceSourcePolicy,ObjectBuiltNotificationPolicy 。

CreaterBuilder() 一般使用 builder.Strategies.AddNew 方法利用 ObjectBuilder 构建策略对象 (例如:builder.Strategies.AddNew<EventBrokerStrategy>(BuilderStage.Initialization))。

2. 子类可以通过重写 protected virtual void AddBuilderStrategies(Builder builder) 来给 ObjectBuilder 添加其他构建策略(在构建对象或者销毁对象时执行的操作)。

3. 初始化 RootWorkItem

这些准备工作做完后,第一件事是创建 RootWorkItem 。RootWorkItem 是通过 CreateRootWorkItem(builder) 完成的,以前面创建的 builder 作为参数:protected internal void InitializeRootWorkItem(Builder builder)。

InitializeRootWorkItem 中首先初始化 RootWorkItem 相关的 Builder 和 Locator (这两个都是 ObjectBuilder 的组件,用与对象创建和依赖注入): this.builder = builder;
this.locator = new Locator();

其次对 rootWorkItem 进行初始化,主要执行以下三个方法:

InitializeFields():设置或初始化 ObjectBuilder 相关的对象: Builder,Locator,ObjectBuiltNotificationPolicy,还有 workItem 的状态。

InitializeState():通过 Guid 生成本 workItem 实例的 ID (ID = Guid.NewGuid().ToString(););

InitializeCollectionFacades():初始化管理 SCSF 核心组件的对象管理集合: ServiceCollection,commandCollection,workItemCollection,workspaceCollection,itemsCollection,smartPartCollection,eventTopicCollection,uiExtensionSiteCollection 。

RootWorkItem 构建完成后有一个可选的过程是创建 IVisualizer (用于在运行时查看 WorkItem 的状态):1 IVisualizer visualizer = CreateVisualizer();
2 if (visualizer != null)
3     visualizer.Initialize(rootWorkItem, builder);

4. 添加服务:

AddRequiredServices() 方法添加的服务有:TraceSourceCatalogService,WorkItemExtensionService,WorkItemTypeCatalogService,SimpleWorkItemActivationService,WindowsPrincipalAuthenticationService,ModuleLoaderService,FileCatalogModuleEnumerator,DataProtectionCryptographyService,CommandAdapterMapService,UIElementAdapterFactoryCatalog 。

AddConfiguredServices() 添加在配置文件中配置的服务,也就是运行我们通过配置的方式决定在 SCSF 框架启动时加载额外服务(Services)。

AddServices() 在 CabApplication 中是一个空的虚拟方法,允许我们创建 CabApplication 的子类来重写该方法以添加需要的 Services 。

5. 验证用户

通过获取在启动过程中注册的 IAuthenticationService 服务来进行用户验证(ObjectBuilder 的具体应用): 1 private void AuthenticateUser()
2 {
3     IAuthenticationService auth = rootWorkItem.Services.Get<IAuthenticationService>(true);
4     auth.Authenticate();
5 }

6. 处理 Shell 程序集(可执行的 Shell Assembly) 1 private void ProcessShellAssembly()
2 {
3     IModuleLoaderService loader = rootWorkItem.Services.Get<IModuleLoaderService>(true);
4     Assembly assembly = Assembly.GetEntryAssembly();

6     if (assembly != null)
7         loader.Load(rootWorkItem, assembly);
8 }

通过 ObjectBuilder 获取已注册的 IModuleLoaderService (默认的是 Microsoft.Practices.CompositeUI.Services.ModuleLoaderService),调用 ModuleLoaderService 的 Load 方法来通过SCSF Attribute(反射和特性)结合 ObjectBuilder 来加载 SCSF 核心组件,包括 Services、 Command 、 SmartParts、UIElement、workspace、Event Broker、State 等,这些属性(Attribute)主要有:ServiceAttribute,ServiceDependencyAttribute,SmartPartAttribute,CommandHandlerAttribute,EventPublicationAttribute,EventSubscriptionAttribute,ModuleDependencyAttribute,StateChangedAttribute,RootWorkItemExtensionAttribute,OptionalDependencyAttribute,TraceSourceAttribute,ComponentDependencyAttribute,WorkItemExtensionAttribute ,以后有时间会专门介绍这些属性的使用。

7. 构建 RootWorkItem (rootWorkItem.BuildUp())

RootWorkItem 通过 builder.BuildUp(locator, type, temporaryID, this, policies) 方法使用 ObjectBuilder 进行构建,前面介绍过 CabApplication 在 CreaterBuilder() 中注册了构建策略 RootWorkItemInitializationStrategy: builder.Strategies.Add(new RootWorkItemInitializationStrategy(this.OnRootWorkItemInitialized), BuilderStage.Initialization),该语句表明在创建 RootWorkItem 时 ObjectBuilder 会执行该策略并调用本类(CabApplication 或者重载的子类)的 OnRootWorkItemInitialized 方法:protected virtual void OnRootWorkItemInitialized()。CabShellApplication (CabApplication 的子类) 重写了该方法: 1 protected sealed override void OnRootWorkItemInitialized()
2 {
3     BeforeShellCreated();
4     shell = RootWorkItem.Items.AddNew<TShell>();
5     AfterShellCreated();
6 }

其中 RootWorkItem.Items.AddNew<TShell>() 语句创建了新的 TShell 主窗体对象(通过 ObjectBuilder 构建)并加入到 RootWorkItem 的 Items 集合中。

8. 通过配置加载模块(LoadModules())

RootWorkItem 构建完成后,SCSF 会根据 IModuleEnumerator 服务来枚举可以加载的模块(确定需要加载哪些模块),SCSF 中提供了两个 IModuleEnumerator 服务:FileCatalogModuleEnumerator(在配置文件中指明要加载哪些模块,默认的配置文件是 ProfileCatalog.xml )和 ReflectionModuleEnumerator(利用反射和 ModuleAttribute 来确定需要加载哪些模块)。同时 SCSF 的Package Guidance 为我们提供了一个 XmlStreamDependentModuleEnumerator ,用于从 Xml Stream 中确定需要加载哪些模块,这个可以用在通过 Web Service 将服务器上的配置发送到客户端的情况。

典型的配置文件示例 ProfileCatalog.xml :<SolutionProfile xmlns="http://schemas.microsoft.com/pag/cab-profile/2.0%22>
  <Section Name="Infrastructure">
    <Modules>
        <ModuleInfo AssemblyFile="GlobalBank.Support.Module.dll" /> 
        <ModuleInfo AssemblyFile="GlobalBank.Infrastructure.Module.dll" />
    </Modules>
  </Section>
  <Section Name="BranchSystems">
    <Dependencies>
      <Dependency Name="Infrastructure" />
    </Dependencies>
    <Modules>
      <ModuleInfo AssemblyFile="GlobalBank.BranchSystems.Layout.dll" Name="BranchSystems.Layout"/>
      <ModuleInfo AssemblyFile="GlobalBank.BranchSystems.Module.dll">
        <Dependencies>
          <Dependency Name="BranchSystems.Layout" />
        </Dependencies>
        <Roles>
          <Role Allow="Greeter"/>
          <Role Allow="Officer"/>
          <Role Allow="BranchManager"/>
        </Roles>
      </ModuleInfo>
    </Modules>
  </Section>
  <Section Name="LinesOfBusiness">
    <Dependencies>
      <Dependency Name="Infrastructure" />
      <Dependency Name="BranchSystems" />
    </Dependencies>
    <Modules>
      <ModuleInfo AssemblyFile="GlobalBank.BasicAccounts.Module.dll">
        <Roles>
          <Role Allow="Officer"/>
          <Role Allow="BranchManager"/>
        </Roles>
      </ModuleInfo>
      <ModuleInfo AssemblyFile="GlobalBank.CreditCardAccounts.Module.dll">
        <Roles>
          <Role Allow="Officer"/>
          <Role Allow="BranchManager"/>
        </Roles>
      </ModuleInfo>
    </Modules>
  </Section>
</SolutionProfile> 


确定要加载那些模块后,就会执行上面 “6. 处理 Shell 程序集”中的过程,利用注册的 IModuleLoaderService 加载模块。SCSF 规定每个 Module 程序集都有一个继承自 ModuleInit 的子类,IModuleLoaderService  会在加载完该 Module 后自动调用其 void Load() 方法,这里是 SCSF 为我们通过的接入点,我们应该在 void Load() 方法中初始化本模块。具体应该初始化什么以后介绍。 

9. 完成对 RootWorkItem 的创建(rootWorkItem.FinishInitialization())
主要是处理 workItem 扩展(以后介绍),并触发创建完成事件:1 protected internal void FinishInitialization()
2 {
3     IWorkItemExtensionService extensionsService = Services.Get<IWorkItemExtensionService>();
4     if (extensionsService != null)
5         extensionsService.InitializeExtensions(this);

7     OnInitialized();
8 }

同时 WorkItem 的 public void InitializeWorkItem() 是一个注入方法,有[InjectionMethod]属性标记,该方法执行时除了执行上面 rootworkItem中的初始化过程外,还会调用 InitializeServices 方法,该方法在 CabApplication 中是空方法,CabApplication 子类可以重写 protected virtual void InitializeServices() 方法,用于在 WorkItem 初始化时加载其他需要的服务。

10. 执行 rootWorkItem 的 run 方法 rootWorkItem.Run();

WorkItem 的 Run 方法直接调用 protected virtual void OnRunStarted() 方法:1 public void Run()
2 {
3     OnRunStarted();
4 }

OnRunStarted 方法触发 RunStarted 事件(public event EventHandler RunStarted) 1 protected virtual void OnRunStarted()
2 {
3     if (this.RunStarted != null)
4     {
5         this.RunStarted(this, EventArgs.Empty);
6     }

8     if (traceSource != null)
9         traceSource.TraceInformation(String.Format(
10             CultureInfo.CurrentCulture,
11                   Properties.Resources.TraceWorkItemRunStarted, ID));
12 }
13 

因此我们可以通过注册 RunStarted 事件或者在 WorkItem 子类中重写 OnRunStarted() 虚方法以便在 SCSF 启动过程中执行自己的操作,这是 SCSF 的又一扩展点。

11. 启动应用 Start()

CabApplication 中的 Start 是一个抽象方法:protected abstract void Start();

子类 FormShellApplication(public abstract class FormShellApplication<TWorkItem, TShell> : WindowsFormsApplication<TWorkItem, TShell>) 重写了Start : 1 protected override void Start()
2 {
3     Application.Run(Shell);
4 }
很简单,这时主窗体就显示出来了,以后的整个操作就是通过用户交互来触发了。

June 25

VSTS作Unit Test的总结

Visual Studio 2005 集成了单元测试框架(Team Test),正好公司规范开发流程,需要提交Unit Test Log,以前都是用Nunit作单元测试,现在既然VS有了自己的UT类,就打算尝试一下,感觉和Nunit用起来差不多,没怎么深入,只是简单总结一下。
VSTS的单元测试(主要是Microsoft.VisualStudio.TestTools.UnitTesting)它支持:
                                          1. 生成测试代码框架;
                                          2. 在IDE中运行测试;
                                          3. 支持从数据库中加载数据的测试;
                                          4. 测试运行完成后,进行代码分析覆盖。

使用VSTS写单元测试的步骤如下:
                                                  1. 创建测试;
                                                  2. 编写测试;
                                                  3. 运行测试;
                                                  4. 代码覆盖。

1.  创建测试

打开解决方案中的.cs类文件(如:StudentManager),在其中的一个方法(如:AddStudent() )上右击,选择“创建单元测试”命令。

在弹出的“创建单元测试”对话框中的“输出项目”下拉框中选择“创建新的Visual C# 测试项目”,单击“确定”按钮,并在“新建测试项目”对话框中输入测试项目的名称(如:MySchoolTest),单击“创建”按钮后,就看见在原有的解决方案中生成了一个新的项目“MySchoolTest”。

测试项目创建成功后,会同时生成4个与测试相关的文件

AuthoringTest.txt                  提供创建测试的说明,包括向项目增加其他测试的说明;

StudentManagerTest.cs         包含AddStudent()的测试,以及测试初始化和测试清除的方法;

MySchoolPro.vsmdi                测试管理文件;

localtestrun.testrunconfig      本地测试运行配置文件。


2.  编写测试

创建测试完毕后,VSTS 为我们自动生成的只是一个测试框架,默认代码中Assert.Inconclusive 表明这是一个未经验证的单元测试。

打开生成的测试文件“StudentManagerTest.cs”,    如示例1:

[TestMethod]
public void AddStudentTest()
{
   
global::MySchool.BLL.StudentManager target = new
               
global::MySchool.BLL.StudentManager();  
   
   
// TODO:初始化为适当的值
   global::MySchool.Models.Student student = null;
   
string expected = null;
   
string actual;
   actual 
= target.AddStudent(student);

   Assert.AreEqual(expected, actual,
                    
"MySchool.BLL.StudentManager.AddStudent 未返回所需的值。");
   Assert.Inconclusive(
"验证此测试方法的正确性。");
}

单元测试中,几个变量的简单介绍:

target         表示测试目标对象,通过这个目标对象可以测试该类中的各个方法;

expected    表示期望得到的值;

actual         表示实际得到的值;


单元测试中,常用的断言方法介绍:

Assert.AreEqual()          测试指定的值是否相等,如果相等,则测试通过;

Assert.Inconclusive()    表示一个未验证的测试;

Assert.IsTrue()              测试指定的条件是否为True,如果为True,则测试通过;

Assert.IsFalse()             测试指定的条件是否为False,如果为False,则测试通过;

Assert.IsNull()               测试指定的对象是否为空引用,如果为空,则测试通过;

Assert.IsNotNull()          测试指定的对象是否为非空,如果不为空,则测试通过;


我们通过对示例1 添加测试所需的初始值,并对断言进行简单的修改后,便得到一个正式的单元测试。

如示例2:

[TestMethod]
public void AddStudentTest()
{
   
global::MySchool.BLL.StudentManager target = new
               
global::MySchool.BLL.StudentManager();  
   
   
// TODO:初始化为适当的值
   global::MySchool.Models.Student student = new
               
global::MySchool.Models.Student();   // 修改1
           student.LoginId = "003";
           student.LoginPwd 
= "test003";
           student.UserStateId 
= 1;
           student.studentName 
= "test003";
           student.studentNo 
= "test003";
           student.Sex 
= "";
           student.ClassID 
= 1;

   
string expected = "学员帐户创建成功!";   //修改2
   string actual;
   actual 
= target.AddStudent(student);

   Assert.AreEqual(expected, actual,
                    
"MySchool.BLL.StudentManager.AddStudent 未返回所需的值。");
   
// Assert.Inconclusive("验证此测试方法的正确性。");
}

这样,便得到了一个正式的单元测试。用断言Assert.AreEqual()比较expected、actual是否相等。
如果相等,测试通过。

配置文件中的设置
由于我们的测试需要和数据库打交道,并且数据库的连接字符串是从配置文件中读取的,所以我们需要在测试项目中添加配置文件(app.config)。

如示例3:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    
<configSections>
    
</configSections>
        
    
<connectionStrings>
             //数据库连接字符串设置
            
<add name="DataBaseOwner"  connectionString="dbo" />
           
<add name="MySchoolConnectionString"  
                    connectionString
=" Data Source=.; Initial Catalog=MySchool;
                                              User ID=sa; Password
=123456 "
                    providerName
="System.Data.SqlClient" />
    
</connectionStrings>
</configuration>


3.  运行测试

打开包含有测试项目的解决方案,在 工具栏 就会出现与测试项目相关的 操作按钮栏。
我们要运行项目中的测试,只需要运行测试项目。

测试项目的运行方式有两种:
                                   
      运行,并启动调试功能;
                                          运行,但不启动调试功能;

1.  当我们运行测试后,在“测试结果”窗口中,将列出项目中所有的测试。

2.  开始的时候,测试会处于“挂起”的状态,测试运行的结果是“通过”或者“失败”。

3.  如果我们要查看测试结果的额外细节时,选定测试项并双击,便打开了详细信息窗口。


4.  代码覆盖

代码覆盖是单元测试的一个关键指标。

代码覆盖:是指单元测试运行时,覆盖了多少代码。

Team Test 包含了一个代码覆盖工具,可以详细解释被执行代码的覆盖率,并突出显示哪些代码被执行,哪些代码没有被执行。

注意: VSTS 在生成单元测试框架时,默认没有启用“代码覆盖”功能。

启用此功能的办法:

1.  首先打开“本地测试运行配置文件” localtestrun.testrunconfig ,在解决方案中。

2.  双击“localtestrun.testrunconfig”文件,弹出该对话框窗口。

3.  在其对话框窗口的左侧选择“代码覆盖率”,然后在右侧的“要检测的项目”中选择要检测的项目。

4.  单击“应用”按钮。


当启用了代码覆盖功能后,再次运行单元测试时:

在“代码覆盖率结果”窗口中,选中“AddStudent()”双击,便可查看代码覆盖率。

在“代码覆盖率结果”窗口中,我们还可以查看单元测试中代码覆盖的块数,以及代码覆盖的百分比信息。

June 13

陈佩斯的花白胡子

陈佩斯老了,猛地一看他那花白的胡子几乎不敢相信,一个真正的表演艺术家,记得童年最爱看的就是他和朱的小品。
队长
别开枪
是我
经典呀
每年春晚,老早就坐在电视机前,就等着他们的出场,可惜后来被CCTV封杀,再也没有上春晚,而春晚对于我也变得可有可无了。
50年代的人,如今也是50多岁的人了,想想自己,也从懵懂无耻的稚童变成了一个一事无成的男人,真有些唏嘘。8406514_15695318
 
 
Lists
No list items have been added yet.
No list items have been added yet.