2010年6月30日 星期三

執行Coded UI Test 出現例外:The playback failed to find the control

過程

在本機執行 Coded UI Test 好好的,改到 Test Agent 上執行,卻發生測試的例外:

Microsoft.VisualStudio.TestTools.UITest.Extension.UITestControlNotFoundException: The playback failed to find the control with the given search properties. Additional Details: 
TechnologyName:  'MSAA'
ControlType:  'Button'
Name:  'Close'
 ---> System.Runtime.InteropServices.COMException: Error HRESULT E_FAIL has been returned from a call to a COM component..

原因

錄製測試時,是使用本機 (Windows 7 En + IE 8) 來錄製的。該測試最終時會關閉瀏覽器。

Test Agent 上則是 (Windows 2003 + IE6)。故執行測試時, IE 6 上並沒有 “Close” 的 Control。因為不同文化的關係,中文 IE 上應該是「關閉」而非「Close」。

解法

目前我並不清楚碰到不同語言版本的瀏覽器時,Coded UI Test應該如何處理,但微軟的 Visual Studio Test tools 有關閉瀏覽器的 API 可直接呼叫,就省去以名稱來找 Control 的困擾

var browser = BrowserWindow.Launch(new System.Uri(this.LauchBrowserParams.Url));
browser.Close();

當然,需要手動修改程式。

2010年6月29日 星期二

Asp.Net 4.0 Xml 轉換錯誤

於執行 TFS build  的結果,找到了下面的錯誤

Error    1    The "TransformXml" task failed unexpectedly.
System.UriFormatException: Invalid URI: The URI is empty.
   at System.Uri.CreateThis(String uri, Boolean dontEscape, UriKind uriKind)
   at System.Uri..ctor(String uriString)
   at Microsoft.Web.Publishing.Tasks.TransformXml.Execute()
   at Microsoft.Build.BackEnd.TaskExecutionHost.Microsoft.Build.BackEnd.ITaskExecutionHost.Execute()
   at Microsoft.Build.BackEnd.TaskBuilder.ExecuteInstantiatedTask(ITaskExecutionHost taskExecutionHost, TaskLoggingContext taskLoggingContext, TaskHost taskHost, ItemBucket bucket, TaskExecutionMode howToExecuteTask, Boolean& taskResult)        0    0  

為什麼呢?完全看不懂。

原來,簡單地說是轉換失敗了。我的 Web.config 中,屬性的值裡的大於 > 這個字元,但這是不合法的。Visaul Studio 並不會抱怨。

例如:

<?xml version="1.0"?>
<configuration>
<appSettings>
  <add key="myKey" value="cdcdcdcdjkcjd>" /> <!-- 值裡有大於字元  -->
</appSettings>
</configuration>

而使用 MSDepoly 工具時,會使用 Xml Transform 來轉換 web.config,就會發生錯誤了。

只是,為什麼錯誤訊息是 Invalid URI 呢?

TFS 建置:Coded UI 自動測試

Coded UI test 是 Visual Studio 2010 的一大賣點,可模擬使用者操作程式。而現在我們已經將 Build Server 建置完畢,當然希望也能進行自動測試。單元測試等原本就在 TFS 2008 上即可執行,而 Coded UI test 卻因模擬使用者登入而需要更多的安裝與設定。

安裝

除了 Team Foundation Server 2010 的安裝外,也需要安裝 Build Agent。在建置的過程中,會進行 Source Control 的取得最新版本,compilatin,以及 Test。在Test 的過程,如果有 Coded UI Test,則必須安裝 Test Agent Controller 及 Test Agent。

設定

Build Server

在Build Server 上,我們必須讓 Build Agent 跑在互動的模式。執行 Team Foundation Administration Console, 點擊「Build Configuration」, 找到Build Service 的 Property。

image

先按「Stop to make change」後,開使調整設定。必須選擇「Interative Process」, 並指定適合的 Credentialsi , 按Start鍵。

image

按Start 鍵後,會跑出下面的視窗。

image

Test Controller

Test Controller 用來控制及收集 Test Agent 執行測試的結果。

執行 Microsfot Visual Studio Test Controller 2010 Configuration,出現下面的視窗。一樣地,使用相同的帳號,並按「Apply Settings」。

image

Test Agent

Test Agent 則是安裝在測試機上,用來模擬使用者在電腦上操作。通常這些測試機都會使用虛擬器,以模擬不同的使用者環境,如 Windows 7 + IE8, Windows Vista + IE7,Mac + Safari。

執行 Microsfot Visual Studio Test Controller 2010 Configuration,出現下面的視窗。一樣地,使用相同的帳號,並按「Apply Settings」。

image

需要注意的是,由於這些安裝 Test Agent 的機器需要「模擬」使用者操作 UI,故不能鎖住電腦(螢幕保護程式),本機一開機後也會直接登入。

自動部署

為了讓後續的網站測試能順利進行,我們必須讓 daily build 時自動在 IIS 上建立應用程式。
在 Build Definition 上,加入 MSBuild 的參數

/p:DeployOnBuild=True
/p:DeployTarget=MsDeployPublish
/p:MSDeployPublishMethod=InProc
/p:CreatePackageOnPublish=True
/p:DeployIisAppPath="預設的網站/EInvoice2" 
/p:MsDeployServiceUrl=localhost

image

修改測試

我測試的是一個 Web Form 的應用程式,在專案中加入一個測試專案。在測試專案中再加入一個 Code UI Test。完成錄製後,簽入到TFS後執行。哇!發生錯誤!

Test method xxx.Tests.CodedUIPermissionsTest.AddDeletePageTest threw exception:
Microsoft.VisualStudio.TestTools.UITest.Extension.PlaybackFailureException: Cannot perform 'SetProperty of Password with value "zPjUJLMG7JyDvQw78l9YYBtInZnoHu7P"' on the control. Additional Details:
TechnologyName:  'Web'
ControlType:  'Edit'
Id:  '_txtPassword'
Name:  '_txtPassword'
TagName:  'INPUT'
---> System.Runtime.InteropServices.COMException: Error HRESULT E_FAIL has been returned from a call to a COM component.

怎麼辦呢?原來當初錄製的帳號與執行測試的帳號是不同的。而為了安全,Coded UI 測試時遇到密碼欄位資料會使用原錄製帳號來加密,執行帳號當然不能解密了。

我這時找 Google 大神,也找不到解法。難道這些內容太新了嗎?看了一下測試專案中UIMap.uitest的內容,哈哈!xml 果然容易了解。將有問題的網頁,將 Encoded=”true” 改成 false, 並將內容改成明碼的密碼後,就解決了這次的問題。

<SetValueAction UIObjectName="UIMap.UIGoogleWindowsInterneWindow.UIDocument.UI_txtPasswordEdit">
  <ParameterName />
  <Value Encoded="true">vDeufj/Hr41sx0RsGlhIlhfWB2QAYMfo</Value> <!-- Encoded :是否加密-->
  <Type>String</Type>
</SetValueAction>

 

 

參考

寫出好程式的好習慣

在寫程式時,哪些習慣必須建立呢?以下列出我認為一個好的程式設計師應該養成的好習慣。

1 Test Driven Development

第一名的,就是我非常推的 TDD了。TDD不只是先寫測試再寫程式這麼簡單的規定而已,背後還包涵了一些情境。

首先,在寫測試程式時,其實就必須先要了解需求。不知道需求,當然寫不出測試。

其二,寫測試時式時,其實我就會開始想「要怎麼讓客戶端呼叫我的程式」,方法是要建立在 instance上呢?還是實作成類別的靜態方法。此時,我們就必須從由使用者的觀點來看,客戶端應該如何使用我們的程式。

其三,寫測試程式時,也會想到客戶端如何與我的程式互動?由兩者的互動關係,進而衍生出 dependent objects,dependency injections, IoC 等。而撰寫測試時,也需要開始建立  mocking object。

最後,TDD將修正我們所寫的類別,不再自行建立物件,而改由客戶端傳入,因此可得到較佳的相依性。好處是物件的生命週期可規劃在IoC一致處理,避免物件到處都是。

2 善用 Static analysis 工具

Visual Studio 中有個 Calculate Code Metrics工具,可計算程式的可維護性指數。此可維護性指數是由循環複雜度、繼承深度、類別結合程度、程式碼行數等四者組合而成。每次重構後,我也習慣來這裡看看可維護性指數是否增加了。如果可維護性指數小於10,那就代表完全沒辦法維護,這樣的程式是連當初的作者也會看不懂的。

FxCopCode analysis 又是另一種常用的功能。它將常見的程式規則寫成 coding rule 並進行分析。分析完後產生如同編譯的錯誤或警告,讓開發人員可以得到提醒並進行修正。

3 使用開發輔助工具

使用良好的開發輔助工具,會讓我們寫程式時事半功倍。Visual Studio 已經內建了許多工具,如 Refactor,Code Snippet等。第三方工具如 CodeRush, ReSharper 等更提供強大的功能,幫助我在搜尋、重構、程式碼分析等地方。

不得不提一下ReSharperCode Analysis功能,可直接在編輯區中背景地指出程式哪裡需要改進。就好像請了一個超強的大師,在身邊耳提面命,不斷地提出修正。久而久之,程式功力當然會進步。

image

4 不要太多的預設計 (Big Design Up Front)

在開發程式時,僅需要符合當下的需求就好。不要預想未來「可能」的需求為何?效能問題?應用程式架構怎樣分才能符合「未來」的需求。

要知道未來不見得發生這些預想的事。做這些預設計不但浪費時間,並可能造成未來程式難以重構,成本自然增加不少。

之前我也犯下不少這樣的錯誤。其中一項頗令我汗顏。當時我將某網站設計成三層式(3-tier)的架構,其中應用程式層(application tier)使用了 remoting的分散式架構。當時想:未來系統一定拆開到不同的伺服器,以符合高擴展性的需求,這些設計未來一定會用到。結果,開發兩年後,不但沒有發生,更糟的是網站還交給另給一組人維護。因為我做了多餘的設計,而這個設計已過了5年還沒發生當時預想的狀況,新進人員都會問當時為何這樣設計?造成交接、維護上的困擾。

5 奧坎剃刀原則 (Occam’s razor)

Occam’s razor 說明了下面的原則:
     假如一個問題有很多種解決辦法,選最簡單的那個

造成系統複雜的原因,其實還可再細分成兩種類型:本質複雜及意外複雜。

本質複雜(essential complexity)

本質上的複雜,是該問題本質上就比較複雜,我們應該採取科學上的方法讓它變簡單。常見科學方法,再分成兩種。

一個是將大而複雜的問題,拆解成多個簡單的小問題,進而一個個解決並得到整體行為。例如微積分,有限元素法。專案管理中的WBS 也是採用了這類的方法。這類方法有個缺點:小問題的總體行為等同於原大問題的行為嗎?

另一種是承認該大而複雜的問題難以拆解成小而簡單的問題。因此使用統計學的方法來統計大而複雜問題的行為。缺點是相當費時秏力,方法不正確,有時反而會得到相反的結果。

意外複雜(accidental complexity)

問題本質性的複雜外,有時候(也是常常)是我們採取的解決方法錯了,反而使得問題更複雜,稱為意外複雜。例如上述我所犯的預設計問題。此時,正確的方法則是將這些意外複雜因素移除掉。

本質複雜與意外複雜有時難以分辨,相當依賴設計人員的經驗。經典常見的意外複雜原因,如下。

  • 我們實作了自己的 Web/Persistence/Messaging/Caching 的framework, 因為找不到比較好用的!!
  • 買整個工具包,即使我們只用到10%。
  • 為了效能,我們將所有的商業邏輯寫到 Stored procedure。(常見吧!)
  • 我們不寫單元測試,因為我們已經花了很多時間在 debug。(真是天才)

這些理由看起來頭頭是道,似乎解決了問題,卻都使用問題更意外複雜。

6 學新的技術

為何要發明新技術?新技術往往是用來解決以前難以解決的問題,進而使用更簡單的方式來解決。以下是一些新的技術,您學會了嗎?
•Reflection
•Regular expressions
•Dependency injection
•Lambda expressions
Extension methods

雖然使用新技術解決問題往往更加容易,但並非一定要採用這些新技術才能解決問題。因此許多「上班族」不願花時間學習,只願使用已經習慣的舊技術來解決。這樣的心態,稱為舒適區(Comfort zone)。這裡不適合討論心理學,話題就此打住。

7 獨立思考

並非所有新技術都能存活下來,故並非所有新技術都值得學習。那要如何分辨呢?

相同的道理,網路上的訊息已經多到「知識爆炸」也難以形容,但我們還是要學習/吸取新知。那要學習哪些知識呢?使用什麼方式學習較有效率呢?

答案就是獨立思考,不要隨廣告/行銷等手法矇騙。要常常想這樣做,比較快嗎?比較有效率嗎?沒有其他方法了嗎?沒有其他觀點了嗎?

結論

寫著寫著,就跳脫程式寫作的範圍了。要如何寫出好程式,其實與個人的思考習慣有密切關係。希望這一篇能對大家有幫助。

後記:感謝保哥提醒,應為 Extension Methods

2010年6月24日 星期四

TPL 學習日記(6): 等待任務1:等待單一任務

等待任務執行完畢後,再接著執行其他的工作,是相當常見的需求。以下介紹幾種常見的需求。

等待某一任務完成

等待某一任務完成後,接著再執行另一任務。這是最基本的工作了。

static void Main(string[] args)
{
  var tokenSource = new CancellationTokenSource();
  var task1 = createTask(tokenSource.Token);
  task1.Start();
  Console.WriteLine("等待執行完畢,或任務取消,或任務發生 Exception");
  task1.Wait();
  Console.WriteLine("執行完畢"); // task1 會執行完畢

  var task2 = createTask(tokenSource.Token);
  task2.Start();
  Console.WriteLine("等待執行完畢(只等3秒),或任務取消,或任務發生 Exception");
  bool completed = task2.Wait(3000);
  Console.WriteLine("執行完畢:{0}", completed); //task2 仍然繼續執行,只是不等了

  var task3 = createTask(tokenSource.Token);
  task3.Start();
  Console.WriteLine("等待執行完畢(只等3秒),或任務取消,或任務發生 Exception");
  completed = task3.Wait(3000, tokenSource.Token);
  Console.WriteLine("執行完畢:{0}, 是否取消:{1}", completed, task3.IsCanceled);
}

執行結果如下

等待執行完畢,或任務取消,或任務發生 Exception
Task Id 1: 0
Task Id 1: 1
Task Id 1: 2
Task Id 1: 3
Task Id 1: 4
執行完畢
等待執行完畢(只等3秒),或任務取消,或任務發生 Exception
Task Id 2: 0
Task Id 2: 1
Task Id 2: 2
執行完畢:False
等待執行完畢(只等3秒),或任務取消,或任務發生 Exception
Task Id 3: 0
Task Id 2: 3
Task Id 3: 1
Task Id 2: 4
Task Id 3: 2
執行完畢:False, 是否取消:False
Task Id 3: 3
Press any key to continue . . .

Task 有 Wait 的方法可以用來等待該任務完成的條件。
方法 說明
Wait() 等待執行完畢
Wait(Int32) 等待執行完畢,但只等一定的時間(微秒)
Wait(Int32, CancellationToken) 等待執行完畢,但只等一定的時間(微秒),或任務取消。

需要注意的是使用 int 當等待時間,當等待時間到達時,就不再等待而程式繼續向下執行,原本執行的任務依然繼續執行。由輸出中的 Task Id 來看,2 與 3 會交互執行一段時間。

2010年6月22日 星期二

ASP.NET 4: Application domain resource management

以往,兩個 Web Application 放在同一個 Application Pool 時,其中一個非常吃資源,往往會影響到另一個Web Application。因此,兩個Web Application 都看起來不正常,常常令人無法了解誰是禍首,也引起一番爭論。

常見的建議,是將這兩個應用程式分開到不同的 Application Pool,再來看看誰是罪魁。這樣一來,勢必有一個Web Application 必須換Application Pool,也就等於應用程式重起啟動,會讓線上使用者跳腳。

以前,這是原罪,一定要經過這樣的痛楚才能得到是非的原因。在 ASP.NET 4 則新增了一項功能,可以在同一個 Application Pool 的 ASP.NET 4 Applications,也可以找到分別的效能計數器。這項功能稱 Application domain resource management,簡寫為 ARM

設定

ARM 預設為非啟動,必須手動修改 C:\Windows\Microsoft.NET\Framework\v4.0.30319\Aspnet.config下,新增一行設定appDomainResourceMonitoring ,如下

<appDomainResourceMonitoring enabled="true"/>
設定完畢後,記得將該 Application Pool 重啟,才會讀取這個新的設定。

效能計數器

在 ASP.NET Apps v4.0 下,會比 V2.0 多了 % Managed Processor Time(estimated) 及 Managed Memory Used(estimated) 這兩組計數器,其下可再細分出每個應用程式的效能。

image

結論

經過這樣的設計,找效能殺手的應用程式,果然比以前來的有效率多了。但為什麼不預設就是啟動的呢?還得手動地加上設定?我猜這也會造成效能上的issue 吧。

REF

http://www.asp.net/learn/whitepapers/aspnet4

What's New in ASP.NET 4 and Visual Web Developer

2010年6月20日 星期日

ASP.NET 4: Routing

雖然已經使用一段時間的 ASP.NET MVC,但畢竟之前使用 ASP.NET WebForm技術寫的程式多到數不清,無法拋棄,但仍然想要使用 MVC 的 Routing 啊。

經由 ASP.NET MVC 的激勵,在 ASP.NET 4.0 WebForm,特地加上了一組新的 Routing 的API,以支援愈來愈多的 SEO 需求。

單一網頁的 Routing

最簡單的Routing,就是簡單地把一個 Url 對應到一個 WebForm 的 aspx。以下就先簡單地建立一個 WebForm Application。

  1. 建立一個 Web Application,並指定名稱寫 TestRoutes
  2. 在 Global.asax.cs 中,修改 Application_Start 如下
void Application_Start(object sender, EventArgs e)
    {
      System.Web.Routing.RouteTable.Routes.MapPageRoute("about", "About", "~/About.aspx");
    }

測試一下網頁,http://localhost:1295/About.aspxhttp://localhost:1295/About 的結果是相同的。

這樣,我們就簡單地將某一 Request 對應到單一的網頁。

單一參數改成 Routing

建立舊有的網頁

我們先新增一個舊 Style 的 WebForm 網頁 Tag.aspx,並指定 Master Page 如下。

image image

網頁的內容如下

<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Tag.aspx.cs" Inherits="TestRoutes.Tag" %>
<asp:Content ID="Content1" ContentPlaceHolderID="HeadContent" runat="server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
  指定 Tag 為 
  <asp:Label Text="text" runat="server" ID="_Tag" />
</asp:Content>

而 CodeBehind 程式如下

using System;

namespace TestRoutes
{
  public partial class Tag : System.Web.UI.Page
  {
    protected void Page_Load(object sender, EventArgs e)
    {
      _Tag.Text = Request["Tag"];
    }
  }
}

測試一下網頁並加上參數 ?Tag=MVC,結果如預期地顯示如下

image

以上是最簡單不過的 WebForm 程式。現在呢,我想要讓 Url 可以讓搜尋引擎更方便地來搜尋(SEO),故想讓 Url 可以可使用 /Tags/MVC 來達到相同的效果。

改成 Routing 型式

第一步,仍然是在 Global.asax.cs 的 Application_Start 上修改 RouteTable,如下

void Application_Start(object sender, EventArgs e)
    {
      System.Web.Routing.RouteTable.Routes.MapPageRoute("about", "About", "~/About.aspx");
      System.Web.Routing.RouteTable.Routes.MapPageRoute("tag", "Tags/{Tag}", "~/Tag.aspx");
    }
接下來,修改 Tag.aspx.cs
protected void Page_Load(object sender, EventArgs e)
{
  //_Tag.Text = Request["Tag"];
  string routeValue = (string)Page.RouteData.Values["Tag"];
  _Tag.Text = routeValue;
}

測試一下網頁 http://localhost:1295/Tags/MVC 果然和之前的結果是一樣的。

image

兩者兼顧

回過頭來測試一下,什麼? http://localhost:1295/Tag.aspx?tag=MVC 反而不能 work? 這非常讓客戶不能接受。我們希望即使SEO 化後,原來的 WebForm 存取方式也能正常。故改 code behind 如下。

protected void Page_Load(object sender, EventArgs e)
{
  _Tag.Text = TagValue;
}

private string TagValue
{
  get
  {
    string fromQuery = Request["Tag"];
    if (string.IsNullOrEmpty(fromQuery))
    {
      string routeValue = (string)Page.RouteData.Values["Tag"];
      return routeValue;
    }
    else
      return fromQuery;
  }
}

經過這次修改後,兩者都可以正常運作了。缺點呢?一看就知道,為了取得使用者要求的 Tag,並且能適用兩種Url 存取方式,必須大動土木地修改 Global.asax.cs 與每個網頁,實在很不人道,而且程式變的更難維護了。之後有空再來解決這一個問題。

使用 RouteValue expression builders

如果在每一頁的 aspx都需要在 code behind 上使用 Page.RouteData來讀取 route value,也太浪費時間了。因此 ASP.NET 4 介紹了一個新的 RouteValue expression builders 來顯示。範例如下

<%@ Page Title="" Language="C#" MasterPageFile="~/Site.Master" AutoEventWireup="true" CodeBehind="Tag.aspx.cs" Inherits="TestRoutes.Tag" %>
<asp:Content ID="Content1" ContentPlaceHolderID="HeadContent" runat="server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
  指定 Tag 為 
  <asp:Label Text="text" runat="server" ID="_Tag" />
  <br />
  使用 RouteValue expression builders方式,得到指定 Tag 為
  <asp:Label runat="server" Text="<%$ RouteValue:Tag %>" />
</asp:Content>

簡單地使用 <%$ RouteValue:routeKey%> 語法,就可以讀取 RouteData 了。

製作 RESTFul的連結

要寫出 RESTFul 的連結其實很簡單,只是需要遵循以下RouteValue expression的規則

<%$RouteUrl:RouteName=yourRouteName,yourRouteKey=yourRoutKeyValue%>

image

其中yourRoutName 及 yourRouteKey 是在 Global.asax.cs 中註冊的 RouteTable,yourRouteKeyValue 即是要查詢的值。

舉例來說,若要在首頁(default.aspx)增加一個連結,可以使用如下的設定

<asp:HyperLink NavigateUrl="<%$RouteUrl:RouteName=tag,Tag=MVC %>" runat="server" Text="MVC" />

結論

在 WebForm 中使用這些 Routing 雖然不是難事,但與 MVC 相比起來,相對來說還是較難使用。在 ASP.NET MVC 中,使用 Html Helper 的 ActionLink 是非常直覺的事啊!而 WebForm 卻要記這個RouteValue expression。

範例下載

Share with Facebook