使用編碼招式(Coding Katas)、BDD和VS2010項目模板
通過編碼招式和行為驅動開發,我受到了一些啟迪,感覺良好。然而,當我意識到如果以后我就用這種方式編寫單元測試、進行開發工作,那會相當痛苦,因為每次都要引入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.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:
...
<ProjectItem ReplaceParameters="true">UnitTest.cs</ProjectItem>
</TemplateContent>
有了這些信息,模板向導就可以在指定文件中有效地搜索并替換所有的參數;如上例中的UnitTest.cs文件。簡單地在.vstemplate文件中增加一個CustomParameter元素,就可以使用自定義參數了:
...
<CustomParameters>
<CustomParameter Name="$TemplateParameter1$" Value="SomeValue"/>
<CustomParameter Name="$TemplateParameter2$" Value="SomeOtherValue"/>
</CustomParameters>
</TemplateContent>
有了這些知識,我們可以把那些零散的東西放到一起,創建我們的BDD單元測試模板。
- 首先,我們要創建一個新項目,作為我們新項目模板的基礎。根據我們的目的,最佳選項是單元測試項目:
創建好這個項目后,我們就有了構建新模板的基礎(當然,這種方法適用于所有的項目類型,不光是單元測試項目)。
- 記住,制作新的項目模板時,該項目中所有的東西都會被包括進去,因此現在我們可以引入所有所需的代碼文件了(在我的例子中,是來自Eric Lee的ContextSpecification.cs):
- 添加所需的程序集引用作為我們項目模板的一部分,在我的例子中,我想使用MSpec程序集:
模板提示:
需要特別注意的是,在部署這個模板的機器上,需要安裝上相同的程序集;因此你應該把引用限制在.NET framework提供的程序集上,或者在GAC中的程序集。這或許意味著,作為你模板的一部分,你必須創建一個安裝程序,以便將某些程序集安裝到GAC 中——或者要求用戶自行安裝所需的程序集。
- 我們要使用適當的模板參數(先前我們查閱過的$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$
{
...
} - 由于我們將在我們的測試類中使用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
}
}
...
} - 下一步是重新定義代碼文件中的測試類,讓它繼承于我們剛剛增加的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
//
}
} - 對于文件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")] - 現在我們準備運行Template Export Wizard,選擇文件->將模板導出為VSIX…:
- 這會打開“將模板導出為VSIX”向導對話框,在這個對話框里,我們可以選擇創建一個項目模板(從解決方案中的項目),或者創建一個新項模板(從所選項目或解決方案中的某個文件)——為了導出一個項目模板,我們選擇解決方案中唯一的項目。單擊下一步>:
- 在向導的下一個界面里,我們可以設置在Visual Studio的“新建項目”對話框中,如何顯示這個模板。適當填寫一些字段并單擊下一步>:
模板提示:
假如不填寫“圖標圖片”和“預覽圖片”這兩個字段,向導會自動幫你生成圖片,你可能會不喜歡這些圖片(看起來像代碼窗口的快照——縮小了的)。建議你創建你自己的圖片,并在這兩個字段中引用你的圖片。
在本例中,我準備了下面這種尺寸的圖片(大小與向導生成的圖片一致): - 在下一個窗口里,填寫好“產品細節”,我們就完成了所有選項。尤其需要注意模板的輸出目錄,以及對話框底下的兩個復選框。
當你單擊完成按鈕的時候,VSIX文件會生成到輸出目錄。
復選框“自動將模板導入Visual Studio”會指示向導是否在導出模板后安裝模板——取決于你想如何創建你的模板, 你可以酌情考慮是否勾選上這一項。眼下我讓它勾選上,因為我想馬上看到結果。
如果你沒有記下輸出路徑,那就有必要勾選上“在資源管理器窗口中顯示輸出文件目錄”——我個人喜歡選上這一項。填寫相應的字段并單擊完成: - 一旦完成這個過程,打開新建項目對話框(要么通過菜單文件->新建->項目,要么通過按下CTRL + Shift + N)并選擇Visual C#項目類型,你會找到新建的BDD Unit Test項目:
- 創建一個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
{
...
}
} - 回顧一下Visual Studio擴展文件(.vsix)的格式,我們可以將項目模板導出向導生成的文件重新命名成.ZIP文件:
- 打開這個ZIP文件,我們會看到以下這種結構;內部的zip文件(在下面例子中是 - BddUnitTest.zip)是我們所關心的,因此在合適的位置解壓縮重命名的zip文件:
- 解壓縮所有的相應的ZIP文件,使用Visual Studio“以…方式打開”的功能打開.vstemplate文件(當提示編輯器類型時選擇XML(文本)編輯器):
- 在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> - 僅僅更新下每個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> - 下一步,重新按照解壓縮的順序對這些文件進行壓縮,首先創建內部的ZIP文件,它包含更新過的.vstemplate文件(在這個例子中就是BddUnitTest.zip),然后把它放到屬于它的文件夾里(與ZIP文件名同名):
- 接著我們將其余文件壓縮至最終的ZIP文件中,這個文件是“更新過的”Visual Studio擴展文件(.vsix):
- 將新建的ZIP文件重新命名為最初由導出向導創建的vsix文件(這里就是BDD Unit Test.vsix):
- 關閉所有的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):
- 在Windows資源管理器中,轉到新建的vsix文件(在這里是BDD Unit Test.vsix)并雙擊它,這會打開Visual Studio擴展安裝器:
- 單擊安裝,安裝過程就會執行并安裝更新過的模板:
- 一旦安裝好擴展,我們就可以啟動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