一起谈.NET技术,使用编码招式(Coding Katas)、BDD和VS2010项目模板

简介:   通过编码招式和行为驱动开发,我受到了一些启迪,感觉良好。然而,当我意识到如果以后我就用这种方式编写单元测试、进行开发工作,那会相当痛苦,因为每次都要引入Eric Lee的ContextSpecification。

  通过编码招式和行为驱动开发,我受到了一些启迪,感觉良好。然而,当我意识到如果以后我就用这种方式编写单元测试、进行开发工作,那会相当痛苦,因为每次都要引入Eric Lee的ContextSpecification。如果我可以简单地选定一个BDD的单元测试项目,然后项目创建后我就拥有了所有项目所需的文件,那就容易多了。稍作查询之后,我找到了一些项目模板导出向导(Project Template Export Wizard)的参考资料,似乎这就是最适合我的解决方案。

  为了能试试这个例子,你要从Visual Studio Gallery上下载并安装Export Template Wizard(在Gallery站点上查询Export Template Wizard)。这是一个微软免费的Visual Studio扩展,可以将一个现有的项目导出成项目模板。

  在我们创建第一个模板前,先看看一些已有的模板,了解一下我们可能需要什么,这对我们来说是很重要的。

安装好Visual Studio后,它的模板位于以下目录:

  • \VisualStudioInstallationDirectory\Common7\IDE\ItemTemplates\Language\Locale\
  • \VisualStudioInstallationDirectory\Common7\IDE\ProjectTemplates\Language\Locale\

  例如,下面这个目录包含了英文版Visual Studio的项目模板:

  • C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE\ItemTemplates\CSharp\1033\

  此外,当你安装一个模板的时候(通常通过双击.vsix文件——微软Visual Studio扩展文件),它会被安装到以下文件夹:

  • \User Documents and Settings Directory\Local Settings\Application Data\Microsoft\VisualStudio\10.0\Extensions

模板提示:

使用注册表编辑器,查看以下键,你会看到所有已安装的Visual Studio 2010扩展:

HKCU\Software\Microsoft\VisualStudio\10.0\ExtensionManager\EnabledExtensions

Visual Studio启动时会自动更新这里的注册项。如果我们删除掉某个扩展(比如,删除某个扩展的目录),Visual Studio下次启动时会更新注册表中的有关项。

  你会看到,所有模板的内容都存储在ZIP文件中,这有助于有条理地“把所有东西都放在一起”。当你检查这些ZIP文件时,你会注意到它们至少都包含一个.vstemplate文件,可以认为这就是模板的配置文件。

  考虑到我们的目的,我们对BasicUnitTest模板中的内容有所兴趣,此模板位于:

C:\Program Files\Microsoft Visual Studio 10.0\Common7

  • \IDE\ItemTemplates\CSharp\1033\BasicUnitTest.zip

  查看VS 2010中的现有模板时,会注意到在代码文件中(比如AssemblyInfo.cs),有一些特殊的关键字。在下面的代码示例中,高亮显示的文本说明了不同的模板参数关键字:

  
  
using System;
using System.Text;
using System.Collections.Generic;
$
if $ ($targetframeworkversion$ == 3.5 ) using System.Linq;$endif$
$
if $ ($targetframeworkversion$ == 4.0 ) using System.Linq;$endif$

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace $rootnamespace$
{
[TestClass]
public class $safeitemname$

{
[TestMethod]
public void TestMethod1()
{
}
}
}

  关键字$rootnamespace$、$safeitemname$以及$safeprojectname$是保留的模板参数:

参数

描述

$rootnamespace$

当前项目的根命名空间。 在项目中添加新项时,此参数用来替换命名空间。

$safeprojectname$

在新建项目对话框中,用户输入的项目名(所有不安全的字符以及空格都会被移除)。

$safeitemname$

在添加新项对话框中,用户输入的名称(所有不安全的字符以及空格都会被移除)。

  查看关联的.vstemplate文件,我们可以看到引用代码文件的地方在ProjectItem元素里,而且ReplaceParameters属性的值被设置为true:

  
  
< TemplateContent >
...
< ProjectItem ReplaceParameters = " true " > UnitTest.cs </ ProjectItem >

</ TemplateContent >

  有了这些信息,模板向导就可以在指定文件中有效地搜索并替换所有的参数;如上例中的UnitTest.cs文件。简单地在.vstemplate文件中增加一个CustomParameter元素,就可以使用自定义参数了:

  
  
< TemplateContent >
...
< CustomParameters >
< CustomParameter Name = " $TemplateParameter1$ " Value = " SomeValue " />
< CustomParameter Name = " $TemplateParameter2$ " Value = " SomeOtherValue " />

</ CustomParameters >
</ TemplateContent >

  有了这些知识,我们可以把那些零散的东西放到一起,创建我们的BDD单元测试模板。

  1. 首先,我们要创建一个新项目,作为我们新项目模板的基础。根据我们的目的,最佳选项是单元测试项目:

    创建好这个项目后,我们就有了构建新模板的基础(当然,这种方法适用于所有的项目类型,不光是单元测试项目)。

  2. 记住,制作新的项目模板时,该项目中所有的东西都会被包括进去,因此现在我们可以引入所有所需的代码文件了(在我的例子中,是来自Eric Lee的ContextSpecification.cs):

  3. 添加所需的程序集引用作为我们项目模板的一部分,在我的例子中,我想使用MSpec程序集:

    模板提示:

    需要特别注意的是,在部署这个模板的机器上,需要安装上相同的程序集;因此你应该把引用限制在.NET framework提供的程序集上,或者在GAC中的程序集。这或许意味着,作为你模板的一部分,你必须创建一个安装程序,以便将某些程序集安装到GAC 中——或者要求用户自行安装所需的程序集。

  4. 我们要使用适当的模板参数(先前我们查阅过的$safeprojectname$)来替换代码文件中出现的命名空间名。同时,我在引用System.Linq的地方,放上了“版本保镖”(译注:只在指定版本的.NET Framework上输出代码块):
        
        
    using System;
    using System.Text;
    using System.Collections.Generic;
    $
    if $ ($targetframeworkversion$ == 3.5 ) using System.Linq;$endif$$ if $ ($targetframeworkversion$ == 4.0 )

    using System.Linq;$endif$
    using Microsoft.VisualStudio.TestTools.UnitTesting;
    namespace $safeprojectname$
    {
    ...
    }
  5. 由于我们将在我们的测试类中使用ContextSpecification,我们要添加一个using声明,并使用适当的模板参数添加基类的定义(这里,我在后面加上了Context这个词,以此我们可以将它同派生测试类区别开来):
        
        
    using System;
    using System.Text;
    using System.Collections.Generic;
    $
    if $ ($targetframeworkversion$ == 3.5 ) using System.Linq;$endif$$ if $ ($targetframeworkversion$ == 4.0 )

    using System.Linq;
    $endif$
    using Microsoft.VisualStudio.TestTools;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    namespace $safeprojectname$
    {
    public class $safeitemname$Context : ContextSpecification
    {
    /// <summary>

    /// The "Given some initial context" method
    /// </summary>
    protected override void Context()
    {
    // setup your class under test
    }
    }
    ...

    }
  6. 下一步是重新定义代码文件中的测试类,让它继承于我们刚刚增加的Context基类。我在这里使用的代码,来自于本文先前描述的保龄球招式解决方案,并用合适的模板参数替换了类名和方法名。
        
        
    /// <summary>
    /// Summary description for $safeitemname$
    /// </summary>
    [TestClass]
    public class $safeitemname$ : $safeitemname$Context
    {
    /// <summary>

    /// The "When an event occurs" method
    /// </summary>
    protected override void BecauseOf()
    {
    //
    // TODO: Add behavior setup (Action) here

    //
    }

    /// <summary>
    /// The "then ensure some outcome" method.
    /// </summary>
    [TestMethod]
    public void TestMethod1()
    {
    //

    // TODO: Add test logic here
    //
    }
    }
  7. 对于文件AssemblyInfo.cs,我们要针对我们创建的项目,替换其中的一些引用(比如程序集的名字等等),然而这次我们可以从现存的模板“定义” 中拷贝已有的AssemblyInfo(我使用“C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE\ProjectTemplates\CSharp\Test\1033\TestProject.zip”)到我们的项目中。
        
        
    using System.Reflection;
    using System.Runtime.CompilerServices;
    using System.Runtime.InteropServices;
    // General Information about an assembly is controlled through the following
    // set of attributes. Change these attribute values to modify the information
    // associated with an assembly.
    [assembly: AssemblyTitle( " $projectname$ " )]
    [assembly: AssemblyDescription(
    "" )]
    [assembly: AssemblyConfiguration(
    "" )]
    [assembly: AssemblyCompany(
    " $registeredorganization$ " )]
    [assembly: AssemblyProduct(
    " $projectname$ " )]
    [assembly: AssemblyCopyright(
    " Copyright ?? $registeredorganization$ $year$ " )]
    [assembly: AssemblyTrademark(
    "" )]
    [assembly: AssemblyCulture(
    "" )]

    // Setting ComVisible to false makes the types in this assembly not visible
    // to COM components. If you need to access a type in this assembly from
    // COM, set the ComVisible attribute to true on that type.
    [assembly: ComVisible( false )]
    // The following GUID is for the ID of the typelib if this project is exposed to COM
    [assembly: Guid( " $guid1$ " )]

    // Version information for an assembly consists of the following four values:
    //
    // Major Version
    // Minor Version
    // Build Number
    // Revision
    //
    // You can specify all the values or you can default the Build and Revision Numbers
    // by using the '*' as shown below:

    [assembly: AssemblyVersion(
    " 1.0.0.0 " )]
    [assembly: AssemblyFileVersion(
    " 1.0.0.0 " )]
  8. 现在我们准备运行Template Export Wizard,选择文件->将模板导出为VSIX…:

  9. 这会打开“将模板导出为VSIX”向导对话框,在这个对话框里,我们可以选择创建一个项目模板(从解决方案中的项目),或者创建一个新项模板(从所选项目或解决方案中的某个文件)——为了导出一个项目模板,我们选择解决方案中唯一的项目。单击下一步>:

  10. 在向导的下一个界面里,我们可以设置在Visual Studio的“新建项目”对话框中,如何显示这个模板。适当填写一些字段并单击下一步>:

    模板提示:

    假如不填写“图标图片”和“预览图片”这两个字段,向导会自动帮你生成图片,你可能会不喜欢这些图片(看起来像代码窗口的快照——缩小了的)。建议你创建你自己的图片,并在这两个字段中引用你的图片。
    在本例中,我准备了下面这种尺寸的图片(大小与向导生成的图片一致):



  11. 在下一个窗口里,填写好“产品细节”,我们就完成了所有选项。尤其需要注意模板的输出目录,以及对话框底下的两个复选框。
    当你单击完成按钮的时候,VSIX文件会生成到输出目录。
    复选框“自动将模板导入Visual Studio”会指示向导是否在导出模板后安装模板——取决于你想如何创建你的模板, 你可以酌情考虑是否勾选上这一项。眼下我让它勾选上,因为我想马上看到结果。
    如果你没有记下输出路径,那就有必要勾选上“在资源管理器窗口中显示输出文件目录”——我个人喜欢选上这一项。填写相应的字段并单击完成:

  12. 一旦完成这个过程,打开新建项目对话框(要么通过菜单文件->新建->项目,要么通过按下CTRL + Shift + N)并选择Visual C#项目类型,你会找到新建的BDD Unit Test项目:

  13. 创建一个BDD Unit Test类型的项目,并打开UnitTest1.cs文件。你会发现从类名继承的模板参数(unittest1Context and unittest1)是小写的,在我看来这不太理想:
        
        
    using System;
    using System.Text;
    using System.Collections.Generic;
    using System.Linq;
    using Microsoft.VisualStudio.TestTools;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    namespace BDDUnitTest1
    {
    public class unittest1Context : ContextSpecification
    {
    ...
    }

    /// <summary>

    /// Summary description for unittest1
    /// </summary>
    [TestClass]
    public class unittest1 : unittest1Context
    {

    ...
    }
    }
  14. 回顾一下Visual Studio扩展文件(.vsix)的格式,我们可以将项目模板导出向导生成的文件重新命名成.ZIP文件:

  15. 打开这个ZIP文件,我们会看到以下这种结构;内部的zip文件(在下面例子中是 - BddUnitTest.zip)是我们所关心的,因此在合适的位置解压缩重命名的zip文件:

  16. 解压缩所有的相应的ZIP文件,使用Visual Studio“以…方式打开”的功能打开.vstemplate文件(当提示编辑器类型时选择XML(文本)编辑器):

  17. 在Visual Studio的XML编辑器中打开.vstemplate文件,你会注意到每个文件对应的ProjectItem元素的TargetFileName属性都是小写的,尽管实际的文件名使用的是骆驼命名法:
        
        
    <? xml version = " 1.0 " ?>
    < VSTemplate xmlns:xsi = " http://www.w3.org/2001/XMLSchema-
    instance " xmlns:xsd= " http: // www.w3.org/2001/XMLSchema"Type="Project" Version="3.0.0" xmlns=" http://sc

    hemas.microsoft.com
    / developer / vstemplate / 2005 " >
    < TemplateData >
    < Name > BDD Unit Test </ Name >
    < Description > A Test project based on Behavior Driven Development (BDD) principles </ Description >
    < Icon > __Template_small.png </ Icon >
    < PreviewImage > __Template_large.png </ PreviewImage >
    < ProjectType > CSharp </ ProjectType >
    < ProjectSubType />
    < SortOrder > 1000 </ SortOrder >
    < DefaultName > BDDUnitTest </ DefaultName >
    < ProvideDefaultName > true </ ProvideDefaultName >
    < EnableLocationBrowseButton > true </ EnableLocationBrowseButton >
    < LocationField > Enabled </ LocationField >
    </ TemplateData >
    < TemplateContent >
    < Project File = " BddUnitTest.csproj " TargetFileName = " BddUnitTest.csproj " ReplaceParameters = " true " >
    < ProjectItem
    TargetFileName
    = " contextspecification.cs "
    ReplaceParameters
    = " true " > contextspecification.cs </ ProjectItem >
    < ProjectItem
    TargetFileName
    = " assemblyinfo.cs "
    ReplaceParameters
    = " true " > properties\assemblyinfo.cs </ ProjectItem >
    < ProjectItem
    TargetFileName
    = " unittest1.cs "
    ReplaceParameters
    = " true " > unittest1.cs </ ProjectItem >
    </ Project >
    < CustomParameters />
    </ TemplateContent >

    </ VSTemplate >
  18. 仅仅更新下每个ProjectItem元素的TargetFileName属性,使用实际文件名的驼峰式大小写(没必要更新ProjectItem元素的文本部分,因为这不会影响向导在创建项目时如何生成文件):
        
        
    <? xml version = " 1.0 " ?>
    < VSTemplate xmlns:xsi = " http://www.w3.org/2001/XMLSchema-
    instance " xmlns:xsd= " http: // www.w3.org/2001/XMLSchema" Type="Project" Version="3.0.0" xmlns=" http://sc

    hemas.microsoft.com
    / developer / vstemplate / 2005 " >
    < TemplateData >
    ...
    </ TemplateData >
    < TemplateContent >
    < Project File = " BddUnitTest.csproj " TargetFileName = " BddUnitTest.csproj " ReplaceParameters = " true " >
    < ProjectItem
    TargetFileName
    = " ContextSpecification.cs "
    ReplaceParameters
    = " true " > contextspecification.cs </ ProjectItem >
    < ProjectItem
    TargetFileName
    = " AssemblyInfo.cs "
    ReplaceParameters
    = " true " > properties\assemblyinfo.cs </ ProjectItem >
    < ProjectItem
    TargetFileName
    = " UnitTest1.cs "
    ReplaceParameters
    = " true " > unittest1.cs </ ProjectItem >
    </ Project >
    < CustomParameters />
    </ TemplateContent >
    </ VSTemplate >
  19. 下一步,重新按照解压缩的顺序对这些文件进行压缩,首先创建内部的ZIP文件,它包含更新过的.vstemplate文件(在这个例子中就是BddUnitTest.zip),然后把它放到属于它的文件夹里(与ZIP文件名同名):

  20. 接着我们将其余文件压缩至最终的ZIP文件中,这个文件是“更新过的”Visual Studio扩展文件(.vsix):

  21. 将新建的ZIP文件重新命名为最初由导出向导创建的vsix文件(这里就是BDD Unit Test.vsix):

  22. 关闭所有的Visual Studio 2010实例,转到文件夹\User Documents and Settings Directory\Local Settings\Application Data\Microsoft\VisualStudio\10.0\Extensions,并删除原先由导出向导创建的相关扩展所在的目录(这里是 \User Documents and Settings Directory\Local Settings\Application Data\Microsoft\VisualStudio\10.0\Extensions\JasPWarE\BDD Unit Test):

  23. 在Windows资源管理器中,转到新建的vsix文件(在这里是BDD Unit Test.vsix)并双击它,这会打开Visual Studio扩展安装器:

  24. 单击安装,安装过程就会执行并安装更新过的模板:

  25. 一旦安装好扩展,我们就可以启动Visual Studio 2010,创建一个新的BDD Unit Test项目并检查代码文件,以此验证我们的更新是否正常工作:
        
        
    using System;
    using System.Text;
    using System.Collections.Generic;
    using System.Linq;
    using Microsoft.VisualStudio.TestTools;

    using Microsoft.VisualStudio.TestTools.UnitTesting;

    namespace BDDUnitTest1
    {
    public class UnitTest1Context : ContextSpecification
    {

    ...
    }

    /// <summary>
    /// Summary description for UnitTest1
    /// </summary>

    [TestClass]
    public class UnitTest1 : UnitTest1Context
    {
    ...

    }
    }

  虽然在最后进行清理工作的时候,有一定量的人工介入,但这有助于揭露更新已有的.vsix文件以及更改模板是多么容易。有了新的模板,创建BDD单元测试就容易多了,因为我们不用担心是否引用了正确的程序集或,或者是否包含了适当的代码文件。你仅需关注于编写实际的测试本身。

  从编码招式到行为驱动开发再到项目模板的旅程,向我们展示了各种各样的实践练习,我们可以用它们在不同层次上提高自己。编码招式可以提高我们的编码技能;行为驱动开发可以提高我们做设计和编写单元测试的方法;项目模板可以改进我们创建代码项目的过程。

  查看英文原文:Using Coding Katas, BDD and VS2010 Project Templates: Part 3

目录
相关文章
|
25天前
|
人工智能 开发框架 C#
C#/.NET/.NET Core技术前沿周刊 | 第 6 期(2024年9.16-9.22)
C#/.NET/.NET Core技术前沿周刊 | 第 6 期(2024年9.16-9.22)
|
25天前
|
人工智能 开发框架 Cloud Native
C#/.NET/.NET Core技术前沿周刊 | 第 9 期(2024年10.07-10.13)
C#/.NET/.NET Core技术前沿周刊 | 第 9 期(2024年10.07-10.13)
|
25天前
|
开发框架 前端开发 API
C#/.NET/.NET Core优秀项目和框架2024年9月简报
C#/.NET/.NET Core优秀项目和框架2024年9月简报
|
25天前
|
数据可视化 NoSQL C#
C#/.NET/.NET Core技术前沿周刊 | 第 8 期(2024年10.01-10.06)
C#/.NET/.NET Core技术前沿周刊 | 第 8 期(2024年10.01-10.06)
|
25天前
|
存储 消息中间件 前端开发
.NET常见的几种项目架构模式,你知道几种?
.NET常见的几种项目架构模式,你知道几种?
|
25天前
|
边缘计算 开发框架 人工智能
C#/.NET/.NET Core优秀项目和框架2024年8月简报
C#/.NET/.NET Core优秀项目和框架2024年8月简报
|
2月前
|
开发框架 前端开发 JavaScript
ASP.NET MVC 教程
ASP.NET 是一个使用 HTML、CSS、JavaScript 和服务器脚本创建网页和网站的开发框架。
38 7
|
2月前
|
存储 开发框架 前端开发
ASP.NET MVC 迅速集成 SignalR
ASP.NET MVC 迅速集成 SignalR
53 0
|
3月前
|
开发框架 前端开发 .NET
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
ASP.NET MVC WebApi 接口返回 JOSN 日期格式化 date format
46 0
|
3月前
|
开发框架 前端开发 安全
ASP.NET MVC 如何使用 Form Authentication?
ASP.NET MVC 如何使用 Form Authentication?