既然單元測試是這麼重要,在上完Agile的課後,小弟開始學習在.Net Framework 要怎麼做單元測試,這一篇算是介紹,也是對自己學習的筆記,我使用的工具是 C# 與 Visual Studio 2012,使用 Java 的朋友,你一定能找到對應的工具與平台,這年頭, M$ 會推出某個功能,都是被 Java Community 逼出來的..ㄎㄎ... 廢話不多說,我用問題與回答來說明單元測試:
1. 單元測試到底是指甚麼? =>預期結果等於測試結果
簡單說,單元測試是對程式的最基本的單位進行測試 ( 最基本單位是要看你自己認定,小弟是非常程序導向的開發人員 i.e. old ,我的通常是 Function ),確認程式能夠如預期的執行。小弟的心得是,因為要單元測試,會強迫自己開始規劃自己的程式,讓 function 真正能比較像一個 function 該做的事,不會通通塞到一起才來拼命 debug line by line (還記得以前老師教的高聚合低耦合嗎?... )
還記得剛才的XP Test Rule "所有程式碼都要有單元測試 !",所以要如何判斷自己有涵蓋夠多的程式? 你可以使用Visual Studio 2012的 "分析程式碼涵蓋範圍"功能 ,馬上就可以看到涵蓋結果。至於你的目標要設為多少,可能要由你的 developer team 自行在definition of done 中定義。
5. 要如何把自己的 Function 跟別的 Interface/Library 隔離? => Fake 機制
有時候測試的時候會希望跟別的 Interface/Library 切割的比較清楚,譬如拿到輸入資料後不寫入資料庫,只要判斷程式邏輯正確,這時要怎麼切割呢? 在 Visual Studio 2012 時加上了 Fake 的機制,請一定要參考這一篇 VS 2012 的 Unit Test與測試總管 的"建立 Fake 組件" 與這一篇 Isolating Code Under Test with Microsoft Fakes ,Fake機制裡面還可以分為 Stub 與 Shim ,各有各的適用對象。以下小弟舉一個簡單的範例,使用的是Shim給大家參考:我要做的是,不去資料庫檢查帳密,我只要確定帳號密碼不一致就可以,所以我用Shim 隔離我的測試程式與實際的程式。
6. 要如何自動Build 自動單元測試? => TFS 現在5人內免費!
如果你是 one person team (獵人的磊扎,就是下圖那一位,1個人可以當5個人),那你可以不用考慮這個了 "持續整合/持續集成"這個議題,你自己應該會持續的 build,並且你的開發環境上就是 releasable build...
如果你不是 one person team,一定會遇到版本控制、自動整合Build、自動測試的議題。在這部分,我是使用免費的tfs,現在是5人以內免費,適合small team (M$可能不知道台灣軟體廠商 >5個工程師的專案是很大的專案 哈...)。這一段的內容我是參考這一本書 軟體測試實戰 Visual Studio & Team Foundation Server (推! 雖然是visual studio 2010 ,但是還是很有參考價值,不知道會不會改版..) 書中的 "10-03 自動化組件與測試" 。使用方式大致是這樣:
前言:Google Maps API 提供服務以來,由於容易開發、API功能多更新快並且又穩定,只要不踩到一些地雷,就不會收錢,所以一直是地圖開發者喜歡用的工具API。這一篇是要介紹,從資料庫取得點位資料套疊到地圖上。我使用的工具是 ASP.Net 使用 Google Map API v3 ,並且用AJAX,是因為如果用標準的asp.net button,會submit/postback 回 server,如果使用者在submit之前就做了一些地圖操作(放大、移動..),就會因為submit to server 會回到初始畫面,這樣的操作模式是滿令人不悅的,所以才需要AJAX方式來處理使用者對地圖的操作。
網頁功能:取得使用者目前座標,繪製地點與精確度的範圍。並根據使用者點選的圖層類型,帶入對應的點位。
瀏覽器限制:因為有用到HTML5,IE8,9確定不行,用 Chrome Ok, IPhone Safari OK
HTML Head區:需要注意的是 style 那一區,因為 Google Map DIV必須指定大小,所以指定在Head裡面。其他就是引用 jquery 與 google map api v3
<head>
<title>Google Maps API v3 and ASP.Net AJAX</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<style type="text/css">
html { height: 100% }
body { height: 100%; margin: 0px; padding: 0px }
#map_canvas { height: 100% }
</style>
<link rel="stylesheet" href="/css/jquery.mobile-1.2.0.min.css" />
<script src="/js/jquery-1.8.2.min.js"></script>
<script src="/js/jquery.mobile-1.2.0.min.js"></script>
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false"></script>
再下來就是控制google maps div 的區域,首先是global的變數
//Global variables
var map; //地圖
var marker; //地圖上的點
var infowindow; //點上面跳出的視窗
var overlays = []; //目前地圖上所有套疊的圖層
var mapcenter = new google.maps.LatLng(25.228664, 121.750202); //預設的地圖中心
//
//取得目前位置
//
function getLocation() {
var x = document.getElementById("Message");
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(showPosition);
//如果要持續取得,用watchPosition
//navigator.geolocation.watchPosition(showPosition);
}
else { x.innerHTML = "Geolocation is not supported by this browser."; }
}
function showPosition(position) {
var x = document.getElementById("Message");
//x.innerHTML = "Latitude: " + position.coords.latitude + "
Longitude: " + position.coords.longitude + "
Accuracy" + position.coords.accuracy;
var newMapCenter = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
map.setCenter(newMapCenter);
//以marker方式出現
setMarker(newMapCenter, "My Current Location" + "
Accuracy is :" + position.coords.accuracy);
//以圓形出現
if (position.coords.accuracy != null) {
DrawCircle(newMapCenter, position.coords.accuracy);
if (position.coords.accuracy < 1000) {
map.setZoom(14);
}
} else {
DrawCircle(newMapCenter, 100);
}
}
繪圖用的function,繪製圓形、繪製單一點座標與視窗(infowindow)訊息
//繪製圓形
function DrawCircle(center,rad) {
var draw_circle;
draw_circle = new google.maps.Circle({
center: center,
radius: rad,
strokeColor: "#FF0000",
strokeOpacity: 0.8,
strokeWeight: 2,
fillColor: "#FFFF00",
fillOpacity: 0.35,
map: map
});
overlays.push(draw_circle);
}
//繪製單一點座標
function setMarker(singleCoord,infoWindowContent) {
marker = new google.maps.Marker({
map: map,
draggable: true,
animation: google.maps.Animation.DROP,
position: singleCoord
});
google.maps.event.addListener(marker, 'click', function () {
infowindow.setContent(infoWindowContent);
infowindow.open(map, this);
});
//
overlays.push(marker);
}
//產生彈跳效果
function toggleBounce() {
if (marker.getAnimation() != null) {
marker.setAnimation(null);
} else {
marker.setAnimation(google.maps.Animation.BOUNCE);
}
}
繪製多個點,提供給asp.net程式呼叫。Javascript Array的格式是['Mount Evans', 59.32522, 18.17002, 4, 'This is Mount Evans.'];,C#要繪製多點的時候,就是去準備出這個多筆陣列,再交給 setMarkers 去繪製。
function setMarkers(markers) {
for (var i = 0; i < markers.length; i++) {
var sites = markers[i];
var siteLatLng = new google.maps.LatLng(sites[1], sites[2]);
var marker = new google.maps.Marker({
position: siteLatLng,
map: map,
title: sites[0],
zIndex: sites[3],
html: sites[4]
});
var contentString = "info window content";
google.maps.event.addListener(marker, "click", function () {
infowindow.setContent(this.html);
infowindow.open(map, this);
if (marker.getAnimation() != null) {
marker.setAnimation(null);
} else {
marker.setAnimation(google.maps.Animation.BOUNCE);
}
});
//
overlays.push(marker);
}
}
清除全部繪製的圖層
function clearMarkers() {
while (overlays[0]) {
overlays.pop().setMap(null);
}
}
另外在.ASPX網頁設計上,只有ASPX的Controls(Button1~4,顯示A~D類的圖層資料),放在UpdatedPanel裡面。Google Map DIV 更要在Form 以外才能正常顯示。控制地圖的HTML Button(Button 6,7)則是單純的HTML button就可以