2014年4月28日 星期一

Unit Test AAA Pattern

單元測試的程式碼有一個AAA Pattern,就是Arrange Act Assert
主要的目的是要讓測試程式碼好讀、好懂、好維護

Arrange:初始化測試環境
Act:執行測試程式
Assert:驗證測試結果

用一個簡單的程式來說明

主程式

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    public class Calculator
    {
        public int Add(int x, int y)
        {
            return x + y;
        }
    }
}

測試程式

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using ConsoleApplication1;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace ConsoleApplication1.Tests
{
    [TestClass()]
    public class CalculatorTests
    {
        [TestMethod()]
        public void AddTest()
        {
            // Arrange
            Calculator calc = new Calculator();
            int x = 1;
            int y = 2;
            int expected = 3;

            // Act
            int actual = calc.Add(x, y);

            // Assert
            Assert.AreEqual(expected, actual);
        }
    }
}

2014年4月24日 星期四

Unit Test 程式結構

先新增一個測試專案

可以看到主要元件是Microsoft.VisualStudio.QualityTools.UnitTestFramework

該元件中常用的Attribute如下

TestClass
用來識別內含測試方法的類別

TestMethod
用來識別測試方法,測試方法必須放置在測試類別中

AssemblyInitialize
用於該組件所有的測試之前,用來配置此組件所佔用的資源

ClassInitialize
用於測試類別的所有測試之前,用來配置該測試類別所使用的資源

TestInitialize
用於測試方法執行之前,用來配置該測試方法中所使用的資源

TestCleanup
用於測試方法執行完成之後,用來釋放該測試方法所佔用的資源

ClassCleanup
用於測試類別中所有的測試完成之後,用來釋放該測試類別所佔用的資源

AssemblyCleanup
用於該組件所有測試類別完成測試之後,用來釋放此組件所佔用的資源

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace UnitTestProject1
{
    [TestClass]
    public class UnitTest1
    {
        [TestClass()]
        public sealed class DivideClassTest
        {
            [AssemblyInitialize()]
            public static void MyAssemblyInitialize(TestContext context)
            {
                Console.WriteLine("MyAssemblyInitialize " + context.TestName);
            }

            [ClassInitialize()]
            public static void MyClassInitialize(TestContext context)
            {
                Console.WriteLine("MyClassInitialize " + context.TestName);
            }

            [TestInitialize()]
            public void MyTestInitialize()
            {
                Console.WriteLine("MyTestInitialize");
            }

            [TestMethod]
            public void TestMethod1()
            {
                Assert.Inconclusive();
            }

            [TestCleanup()]
            public void MyTestCleanup()
            {
                Console.WriteLine("MyTestCleanup");
            }

            [ClassCleanup()]
            public static void MyClassCleanup()
            {
                Console.WriteLine("MyClassCleanup");
            }

            [AssemblyCleanup()]
            public static void MyAssemblyCleanup()
            {
                Console.WriteLine("MyAssemblyCleanup");
            }
        }
    }
}

2014年4月23日 星期三

Unit Test 筆記







http://blog.developer.idv.tw/2014/06/moq.html

http://blog.developer.idv.tw/2014/06/autofac-ioc.html
http://blog.developer.idv.tw/2014/06/controller.html

10.

Unit Test Assert 類別


AreEqual 確認兩個指定的物件相等
如果這些物件都不相等,判斷提示就會失敗
AreNotEqual 確認兩個指定的物件不相等
如果這些物件都相等,判斷提示就會失敗
AreSame 確認兩個指定的物件變數參考相同的物件
如果它們參考不同的物件,判斷提示就會失敗
AreNotSame 確認兩個指定的物件變數參考不同的物件
如果它們參考相同的物件,判斷提示就會失敗
IsTrue 驗證指定的條件是 true
如果條件為 false,判斷提示就會失敗。
IsFalse 驗證指定的條件是 false
如果條件為 true,判斷提示就會失敗
IsNull 確認指定的物件是 null
如果它不是 null,判斷提示就會失敗
IsNotNull 確認指定的物件不是 null
如果它是 null,判斷提示就會失敗
IsInstanceOfType 確認指定的物件是指定之型別的執行個體
如果此型別不在物件的繼承階層架構內,判斷提示就會失敗
IsNotInstanceOfType 確認指定的物件不是指定之型別的執行個體
如果此型別位於物件的繼承階層架構內,判斷提示就會失敗
Fail 判斷提示失敗,但不檢查任何條件
Inconclusive 表示無法驗證判斷提示


AllItemsAreInstancesOfType 確認指定之集合中的所有項目都是指定之型別的執行個體
如果任何項目的型別不在其繼承階層架構內,判斷提示就會失敗
AllItemsAreNotNull 確認指定之集合中的所有項目都不是 null
如果有任何項目是 null,判斷提示就會失敗
AllItemsAreUnique 確認指定之集合中的所有項目都是唯一的
如果集合中有任兩個項目相等,判斷提示就會失敗
AreEqual 確認兩個指定的集合相等
如果這些集合都不相等,判斷提示就會失敗
AreNotEqual 確認兩個指定的集合不相等
如果這些集合都相等,判斷提示就會失敗
AreEquivalent 確認兩個指定的集合對等
如果這些集合都不對等,判斷提示就會失敗
AreNotEquivalent 確認兩個指定的集合不對等
如果這些集合都對等,判斷提示就會失敗
Contains 確認指定的集合包含指定的項目
如果此項目不在集合中,判斷提示就會失敗
DoesNotContain 確認指定的集合不包含指定的項目
如果此項目位於集合中,判斷提示就會失敗
IsSubsetOf 確認第一個集合是第二個集合的子集
IsNotSubsetOf 確認第一個集合不是第二個集合的子集


Contains 確認第一個字串包含第二個字串
這個方法會區分大小寫
StartsWith 確認第一個字串以第二個字串開始
這個方法會區分大小寫
EndsWith 確認第一個字串以第二個字串結束
這個方法會區分大小寫
Matches 確認指定的字串符合規則運算式
DoesNotMatch 確認指定的字串不符合規則運算式


Visual Studio Unit Test Generator擴充套件

之前Visual Studio中的產生單元測試的右鍵功能,因為和MSTest框架的相依性太高,在VS2013的時後拿掉了
如果要在VS2013中繼續使用這個功能,可以安裝Unit Test Generator擴充套件
首先在點選工具-->擴充功能和更新

搜尋Unit Test即可找到該擴充套件

在要加入單元測試的地方按右鍵,就會看到多出了一個單元測試產生器

可以設定要產生的規則,例如專案、類別、函式的命令方式

最後產生的結果


TeamCity整合Source Monitor報表

Source Monitor是用來分析程式碼度量的工具,先到官網下載

安裝後的預設路徑為C:\Program Files (x86)\SourceMonitor

先淮備一個用來產生報告的設定檔SourceMonitorCommands.xml

<?xml version="1.0" encoding="utf-8"?>
<sourcemonitor_commands>
    <write_log>true</write_log>
    <command>
        <project_file>SourceMonitor.smp</project_file>
        <checkpoint_name>Baseline</checkpoint_name>
        <project_language>C#</project_language>
        <!-- 要分析專案的相對路徑 -->
        <source_directory>..</source_directory>
        <source_subdirectory_list>
            <exclude_subdirectories>true</exclude_subdirectories>
            <source_subtree>bin\</source_subtree>
            <source_subdirectory>obj\</source_subdirectory>
        </source_subdirectory_list>
        <parse_utf8_files>True</parse_utf8_files>
        <ignore_headers_footers>True</ignore_headers_footers>
        <export>
            <!-- 最後輸出的檔名 -->
            <export_file>SourceMonitorReport.xml</export_file>
            <export_type>2</export_type>
        </export>
    </command>
</sourcemonitor_commands>

因為輸出的是XML格式,所以需要透過XSL轉成HTML格式
先下載轉換的工具msxsl.exe

SourceMonitor.xsl
<?xml version="1.0" encoding="utf-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns="http://www.w3.org/TR/xhtml1/strict">
  <xsl:output method="html"/>
  
  <xsl:template match="/">
  <html>
        <head>
        <style type="text/css">
    /* 
 Web20 Table Style
 written by Netway Media, http://www.netway-media.com
*/
    table
    {
        border-collapse: collapse;
        border: 1px solid #666666;
        font: normal 11px verdana, arial, helvetica, sans-serif;
        color: #363636;
        background: #f6f6f6;
        text-align: left;
    }
    caption
    {
        text-align: center;
        font: bold 16px arial, helvetica, sans-serif;
        background: transparent;
        padding: 6px 4px 8px 0px;
        color: #CC00FF;
        text-transform: uppercase;
    }
    thead, tfoot
    {
        background: url(bg1.png) repeat-x;
        text-align: left;
        height: 30px;
    }
    thead th, tfoot th
    {
        padding: 5px;
    }
    table a
    {
        color: #333333;
        text-decoration: none;
    }
    table a:hover
    {
        text-decoration: underline;
    }
    tr.odd
    {
        background: #f1f1f1;
    }
    tbody th, tbody td
    {
        padding: 5px;
        padding-left: 10px;
    }
    td.sectionheader
    {
        color: Navy;
        font-size: 20px;
        font-weigth: bold;
        text-align: center;
    }
</style>
        </head>
        <body>
    <xsl:apply-templates select="//SourceMonitorComplexitySummary" />     
    </body>
    </html>
  </xsl:template>
  
 <!-- The most complex methods -->
  <xsl:template match="SourceMonitorComplexitySummary">
    <table class="section-table" cellpadding="2" cellspacing="0" border="0" width="98%">
      <!--<colgroup>
        <col width="300px" />
        <col width="50px" />
        <col />
        <col width="50px" />
      </colgroup>-->
      <tr>
        <td class="sectionheader" colspan="4">
           SourceMonitor - <xsl:value-of select="count(MostComplexMethods/Method)" /> Most Complex Methods
        </td>
      </tr>
      <tr>
        <th class="header-label">
          Complexity
        </th>
        <th class="header-label">
          File
        </th>
        <th class="header-label">
          Method
        </th>        
        <th class="header-label">
          Line
        </th>
      </tr>
      <xsl:apply-templates select="MostComplexMethods/Method" />
    </table>
    
    <br/>
    
    <!-- The most deeply nested code blocks -->
    <table class="section-table" cellpadding="2" cellspacing="0" border="0" width="98%">
      <!--<colgroup>
        <col width="50px"/>
        <col />
        <col width="50px" />
      </colgroup>-->
      <tr>
        <td class="sectionheader" colspan="4">           
           SourceMonitor - <xsl:value-of select="count(MostDeeplyNestedCode/Block)" /> Most Deeply Nested Code Blocks
        </td>
      </tr>
      <tr>
        <th class="header-label">
          Depth
        </th>
        <th class="header-label">
          File
        </th>
        <th class="header-label">
          Line
        </th>
      </tr>
      <xsl:apply-templates select="MostDeeplyNestedCode/Block" />
    </table>
  </xsl:template>

  <!-- Complex Method List -->
  <xsl:template match="MostComplexMethods/Method">
      <tr>
        <xsl:if test="position() mod 2 = 1">
          <xsl:attribute name="class">section-oddrow odd</xsl:attribute>          
        </xsl:if>
        <td>
          <xsl:value-of select="Complexity" />
        </td>
        <td >
          <xsl:value-of select="File" />
        </td>
        <td>
          <xsl:value-of select="Name"/>
        </td>        
        <td>
          <xsl:value-of select="Line" />
        </td>
      </tr>
  </xsl:template>

  <!-- Deeply Nested Blocks List -->
  <xsl:template match="MostDeeplyNestedCode/Block">
      <tr>
        <xsl:if test="position() mod 2 = 1">
          <xsl:attribute name="class">section-oddrow odd</xsl:attribute>
        </xsl:if>
        <td>
          <xsl:value-of select="Depth" />
        </td>
        <td>
          <xsl:value-of select="File" />
        </td>
        <td>
          <xsl:value-of select="Line" />
        </td>
      </tr>
  </xsl:template>

</xsl:stylesheet>


SourceMonitorSummaryGeneration.xsl
<?xml version="1.0" encoding="UTF-8" ?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
  
  <!--- SourceMonitor Summary Xml File Generation created by Eden Ridgway -->
  <xsl:output method="xml"/>
  
  <xsl:template match="/">
    <xsl:apply-templates select="/sourcemonitor_metrics" />
  </xsl:template>
  
  <!-- Transform the results into a simpler more intuitive and summarised format -->
  <xsl:template match="sourcemonitor_metrics">
      <SourceMonitorComplexitySummary>
        <MostComplexMethods>
          <xsl:call-template name="MostComplexMethods"/>
        </MostComplexMethods>
        
        <LinePerMethod>
          <xsl:call-template name="LinesPerMethod"/>
        </LinePerMethod>

        <MostDeeplyNestedCode>
          <xsl:call-template name="MostDeeplyNestedCode"/>
        </MostDeeplyNestedCode>
        
      </SourceMonitorComplexitySummary>
  </xsl:template>

  <!-- Complexity Metrics -->
  <xsl:template name="MostComplexMethods">
    <xsl:for-each select=".//file">
      <xsl:sort select="metrics/metric[@id='M10']" order="descending" data-type="number" />
      
      <xsl:choose>
        <xsl:when test="position() &lt; 16">
           <Method>
              <File><xsl:value-of select="@file_name"/></File>
              <Name><xsl:value-of select="metrics/metric[@id='M9']"/></Name>
              <Line><xsl:value-of select="metrics/metric[@id='M8']"/></Line>
              <Complexity><xsl:value-of select="metrics/metric[@id='M10']"/></Complexity>
           </Method>
        </xsl:when>
      </xsl:choose>
    </xsl:for-each>
  </xsl:template>

  <!-- Complexity Metrics -->
  <xsl:template name="LinesPerMethod">
    <xsl:for-each select=".//file">
      <xsl:sort select="metrics/metric[@id='M5']" order="descending" data-type="number" />
      
      <xsl:choose>
        <xsl:when test="position() &lt; 16">
           <Method>
              <File><xsl:value-of select="@file_name"/></File>
              <Lines><xsl:value-of select="metrics/metric[@id='M5']"/></Lines>
           </Method>
        </xsl:when>
      </xsl:choose>
    </xsl:for-each>
  </xsl:template>

  <!-- Nesting Metrics -->
  <xsl:template name="MostDeeplyNestedCode">
    <xsl:for-each select=".//file">
        <xsl:sort select="metrics/metric[@id='M12']" order="descending" data-type="text" />
      
        <xsl:choose>
          <xsl:when test="position() &lt; 16">
             <Block>
                <File><xsl:value-of select="@file_name"/></File>
                <Depth><xsl:value-of select="metrics/metric[@id='M12']"/></Depth>
                <Line><xsl:value-of select="metrics/metric[@id='M11']"/></Line>
             </Block>
          </xsl:when>
        </xsl:choose>
    </xsl:for-each>
  </xsl:template>
  
</xsl:stylesheet>


透過批次檔得到HTML格式
SourceMonitor.exe /C SourceMonitorCommands.xml
msxsl.exe SourceMonitorReport.xml SourceMonitorSummaryGeneration.xsl -o SourceMonitorSummaryGeneration.xml
msxsl.exe SourceMonitorSummaryGeneration.xml SourceMonitor.xsl -o SourceMonitorResult.html

在TeamCity上面利用Command Line的Build Step來產出檔案
方便請見所以建立一個SourceMonitor的資料夾,把相關的執行檔和設定檔都放進去

新增一個建置報告
給一個用來識別的名稱和輸入產生的檔案

在一般設定中的Artifact Paths加入輸出的檔案


設定正確的話,建置後就可以看到這個報表了


2014年4月22日 星期二

TeamCity整合Code Coverage報表

首先加入一個MSTest的Build Step

這裡以VS2013為例,選擇要執行單元測試的專案元件

拉到最下面後可以選擇Code Coverage的工具,這裡以dotCover為例子

主要就是輸入Filter,+:加入,-:排除,注意不用輸入副檔名

建置成功後就會看到單元測試覆蓋率的報表了

TeamCity整合FxCop報表

FxCop 10的安裝檔在Microsoft Windows SDK中,所以首先要先下載Windwos SDK

SDK安裝好後,安裝檔的路徑在C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\FXCop\FxCopSetup.exe
由於VS2013已整合FxCop 12版,所以這裡以FxCop 12版為例
安裝好VS2013後,FxCop執行檔的路徑在
FxCop的路徑在C:\Program Files (x86)\Microsoft Visual Studio 12.0\Team Tools\Static Analysis Tools\FxCop

在Build Step中新增一個FxCop Runner

設定內容主要是指定FxCop執行檔的路徑
和要分析的Assembly檔案位置
C:\Program Files (x86)\Microsoft Visual Studio 12.0\Team Tools\Static Analysis Tools\FxCop
比較方便的方式是到Build Agent上面設定buildAgent.properties加入
system.FxCopRoot=C\:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\Team Tools\\Static Analysis Tools\\FxCop
就可以用Autodetect而不用指定路徑了
P.S. 注意跳脫符號

建置成功後就會多出一個FxCop的數據
點擊連結或是右上角的Code Inspection就會轉入FxCop報表的詳細頁面


Visual Studio 程式碼分析

Visual Studio內建的程式碼分析,功能和Fxcop一樣用來對編譯出來的的Assembly做分析
這裡以一個簡單的網站當例子

選擇空白的MVC專案

在專案設定中的程式碼分析有一些預設的規則集

可以新增自訂的規則集

記得在屬性視窗中去修改這個規則集的顯示名稱

調整自訂的規則集

就可以在剛剛專案屬性中選擇自訂的規則集

在專案上面按右鍵選擇分析

又或是在選擇工具列上的分析,快速鍵為Alt + F11

建置後就會跑出分析結果,該項目上點兩下就會切換到引發該訊息的原始碼處

如果要知道這條規則更詳細的說明,就點擊左上方的規則編號
如果要先隱藏訊息的話,就在動作上面點一下,選擇隱藏訊息
這裡以在原始程式檔中為例

就會在原始檔中加入SuppressMessage Attribute
記得要在屬性Justification中加入隱藏這個訊息的原因

namespace DemoWeb
{
    public class MvcApplication : System.Web.HttpApplication
    {
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "DemoWeb不處理")]
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            RouteConfig.RegisterRoutes(RouteTable.Routes);
        }
    }
}

2014年4月9日 星期三

Visual Studio 新增擴充套件

建立好專案和項目範本後,雖然只是放到固定的資料夾就可以使用
但如果要和團隊分享的話,可以考慮新增一個擴充套件比較方便

首先要先安裝Visual Studio SKD,這裡以2013版為例
http://www.microsoft.com/en-us/download/details.aspx?id=40758


安裝後開新專案在擴充性下面就會有一個VSIX專案

先把專案資訊填一填

這邊是選擇可以安裝這個擴充套件的Visual Studio版本

這邊是選擇要安裝的項目,也是主要設定的項目

先新增一個專案範本,選擇之前建立的範本

再新增一個項目範本,選擇之前建立的範本

設定好後會自動把範本複製進來,基本這樣就完成了


但是範本會和預設的範本混在一起,為了方便起來還是建立一個自訂的分類比較好找
所以手動調整一下,新增一個資料夾就行了,資料夾名稱就要自訂分類的名稱

設定好了建置一下方案,就可以得到這個擴充套件了

延伸閱讀

新增專案範本
http://blog.developer.idv.tw/2014/04/visual-studio.html

新增項目範本
http://blog.developer.idv.tw/2014/04/visual-studio_9.html

參考連結
http://timheuer.com/blog/archive/2010/05/03/create-vsix-files-with-visual-studio-template-deployment.aspx

http://msdn.microsoft.com/zh-tw/library/ms247119.aspx

Visual Studio 新增項目範本

除了專案範本之外,也可以用同樣的方式打包出項目範本
先開一個Console專案,沒有特別的原因,只是為了環境簡單而已

以最常建立的類別為例

修改成適合StyleCop的格式

//-----------------------------------------------------------------------
// <copyright file="Class1.cs" company="developer.idv.tw">
//    Copyright © 2014 developer.idv.tw all rights reserved.
// </copyright>
//-----------------------------------------------------------------------

namespace ConsoleApplication1
{
    using System;

    /// <summary>
    /// Class1 Class
    /// </summary>
    public class Class1
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="Class1"/> class
        /// </summary>
        public Class1()
        {
        }
    }
}

修改好了一樣選擇左上角的檔案>>匯出範本


這次選擇匯出項目範本


選擇要匯出的檔案範本


如果這個項目需要相依於特定元件的話,就要在這裡勾選起來


最後輸入這個項目範本的資訊就建立好了


自訂項目範本預設的位置也是在我的文件下面


新增項目的時後就會看到這個範本


但和預設的範本放在一起不太好找,所以也自訂一個分類來放
到Visual C#資料夾下面新增一個資料夾,名稱就是要自訂的分類名稱