2012年12月23日

初嚐 JUnit

所謂測試驅動開發 (Test-Driven Development),就是要先寫測試再寫程式,透過單元測試的撰寫除了對需求規格和設計方式更加深入了解之外,更能確保程式寫完後可以馬上進行自動化測試。

Erich Gamma 和 Kent Beck 就替 Java 開發了一個 JUnit 框架提供單元測試程式的撰寫。 

下圖為此框架的核心:
 
來源: http://junit.sourceforge.net/doc/cookstour/cookstour.htm 

從此類別圖當中可以歸納出幾個有名的設計模式:

命令模式

以 Test 作為抽象介面,其中只有 run() 這個方法用來執行命令的動作,而繼承了 Test 介面的 TestCase 類別,將視為一個具體的命令,並實作了 run() 方法來執行測試操作。

樣板方法模式

然而,在 TestCase 類別當中的 run() 方法實際上是以setUp() -> runTest() -> tearDown() 的順序分別呼叫這三個方法的樣板方法,主要對測試動作做操作的是 runTest() 方法,會在 TestCase 類別當中由框架撰寫者安排好(如何安排待會再說),而框架使用者只須要繼承 TestCase 類別後,針對此單元測試操作之前(可能要初始化一個測試所需要用到的物件)和之後(在測試結束後要消滅測試所用到的物件)必須做處理的動作將之分別寫在 setUp() 和 tearDown() 這兩個掛勾方法當中就可以了。

合成模式

透過此模式可以隨心所欲的將實作了 Test 介面的 TestCase 和 TestSuite 給組合起來。TestSuite 類別當中有一個 Collection<test> 的成員,並可利用 add() 方法來將不同的 Test 組合起來,而其 run() 方法的實作方式就是將 Collection<test> 當中 Test 型態的物件以迭代的方式執行一遍。


從以上可以得知,Test 介面的 run() 方法分別會被 TestCase 和 TestSuite 這兩個繼承他的類別 Override 並實作,而 TestCase 是以樣板方法的方式實作,TestSuite 則是將組合好的 Test 以迭代的方式全部執行一遍,並且最終都是執行到 TestCase 的 run() 方法。

因此,所有的測試操作動作的都集中在 TestCase 的 run() 方法當中,而在此方法當中會執行測試操作動作的關鍵則落到了 runTest() 方法當中。此方法已由框架撰寫者安排好了,其使用的手法是以 Reflection 的方式找到與此 TestCase 的 fName 屬性同名的 public 方法後執行之,目的是為了減少過多的 Unit Test 使得類別越寫越多過度雜亂,透過如此方式便能在同一個 TestCase 當中寫多個 Unit Test(每個都以 testXXX 為命名方式,並以 public 宣告之),並由框架透過該 TestCase 物件的 fName 反射並執行之。

2012年11月8日

Perl Reference

取得參照

規則1:若你在變數之前加上「\」,即可取得該變數的參照。
$aref = \@array; # $aref now holds a reference to @array
$href = \%hash;  # $href now holds a reference to %hash
$sref = \$scalar; # $sref now holds a reference to $scalar
規則2:以「[ ITEMS ]」的方式建立新的匿名陣列,並回傳其參照;以「{ ITEMS }」的方式建立新的匿名雜湊,並回傳其參照。
$aref = [ 1, "foo", undef, 13 ]; # $aref now holds a reference to an array
$href = { APR => 4, AUG => 8 };  # $href now holds a reference to a hash
以下兩種做法的結果皆相同:
# This:
 $aref = [ 1, 2, 3 ];

# Does the same as this:
 @array = (1, 2, 3);
 $aref = \@array;
因此若你隨手即以「[]」建立一個新的匿名陣列而不將其存到變數當中,你往後將不知道該如何取得此陣列的內容,雜湊亦如此。

使用參照

規則1:使用捲括號「{}」來使用陣列參照,因此你可以寫成 @{$aref} 來取代 @array。
@a  @{$aref}  #An array
reverse @a reverse @{$aref} #Reverse the array
$a[3]  ${$aref}[3]  #An element of the array
$a[3] = 17; ${$aref}[3] = 17 #Assigning an element
雜湊規則如同陣列
%h   %{$href}  #A hash
keys %h   keys %{$href}  #Get the keys from the hash
$h{'red'}  ${$href}{'red'}  #An element of the hash
$h{'red'} = 17  ${$href}{'red'} = 17 #Assigning an element
規則2:${$aref}[3] 可用 $aref->[3] 來取代; ${$href}{red} 可用 $href->{red}來取代。 若 $aref 存的是某一陣列的參照,則 $aref->[3] 表示為此陣列的第四個元素。千萬不要把 $aref->[3] 與 $aref[3] 這兩者搞混,$aref[3] 當中的 $aref 不過是暫時取代了 @aref 的虛偽表示法;同樣的道理也發生在雜湊當中,不要把$href->{red} 與 $href{red} 給搞混,後者是 %href 的虛偽表示法。

簡單範例

@a = ( [1, 2, 3],
 [4, 5, 6],
 [7, 8, 9]
 );
@a 陣列有三個元素,每個元素皆以參照存放。 $a[1] 即可取得 (4, 5, 6) 這個陣列的參照,因此可進一步使用 $a[1]->[2] 的方式取得該陣列的第三個元素,即可傳回「6」。同理 $a[0]->[1] 會傳回「2」。 這是一個二維陣列的例子,你能以 $a[ROW]->[COLUMN] 的方式取值,卻很麻煩。因此多維陣列可以再將其縮寫為 $a[ROW][COLUMN],將「->」省略。

翻譯自:http://perldoc.perl.org/perlreftut.html

2012年11月1日

Learning Perl 5


l   雜湊(hash),陣列以數字編索引,雜湊以鍵(key)為索引值
l   串列(list);陣列(array);散列(hash)
l   雜湊是一個無序集合,就是一些鍵/值對(key-value pair)的組合,鍵和值都是任意的「純量」,但是「鍵」永遠會被轉換成字串
l   因此數值運算「50/20」的結果為2.5,轉成字串則為「”2.5”」,因此鍵則為2.5
l   雜湊語法:$hash{$key_name} = $value_name;
l   整份雜湊宣告用「%hash_name」,亦可將其指派給陣列,此時為串列語境,順序會被打亂,但值永遠黏在其對應的鍵之後
l   reverse反轉雜湊後,所有鍵值也將被反轉,變成以值找鍵的雜湊
l   雜湊串列當中,可用大箭號「=>」取代逗號「,」,一般將大箭號左邊視為鍵;右邊視為值。但其與逗號並無差異,僅供方便程式員編修
l   大箭號左邊的bareword會自動加上引號「””」,你不加Perl也會自動加上
l   同理,$hash{bareword} 也可行
l   keys函式會傳回雜湊中的所有鍵;values函式則傳回所有值。若無值則傳回空串列,就算正個雜湊順序不定,但值一定會對應到鍵的順序
l   上兩函式若在純量語境下則傳回元素個數
l   exists函式用來判斷鍵是否存在雜湊中,若值為「undef」也表示存在,傳回truedelete函式用來將鍵值從雜湊中移除,因此再用exists函式判斷則為false
l   雙引號內的特殊字符包含「$@\」,因此無法在雙引號內安插雜湊,因為不支援「%
l   %ENV存放系統環境的資訊

2012年10月23日

Learning Perl 4


l   左右角括號為「整列輸入運算符」,中間夾檔案代號(filehandle)
l   以下程式碼做了:讀取標準輸入、將它存入變數、檢查變數的值是否有定義
while (defined($line = <STDIN>)){
        print “$line”;
}
l   以上程式碼可簡寫為(此簡寫法只限定whileforeach迴圈使用)
while (<STDIN>){
           print “$_”;
}

        foreach (<STDIN>){
           print “$_”;
        }
l   以上方法在while迴圈是一次讀一列;foreach則是在串列語境下一次讀入所有輸入,因此當讀入的檔案越大,foreach將越慢顯示
l   調用引數(invocation argument),命令列上跟在程式名稱後的幾個「單字」
l   調用引數中的「-」連字號代表「標準輸入串流」
l   @ARGVPerl直譯器事先建立的特殊陣列,用來存調用引數所組成的串列
l   鑽石形運算符(diamond operator)<>。用來讀取@ARGV所存的檔案串列,若為空串列則改用標準輸入串流
while (defined($line = <>)){
        chomp($line);       
        print “$line”;
}

while (<>){
   chomp;
   print “$_”;
}
l   當調用引數超過1個時,將會合併為1個檔案,因此不用擔心中間間斷問題
l   鑽石形運算符通常一次處理所有輸入,所以程式當中看到兩個應該是個錯誤
l   鑽石形運算符若無法開啟某個檔案時,會顯示訊息,並直接跳到下一個檔案
l   $0Perl直譯器預設存放程式自己的檔名
l   print @array; 無空格;print “@array”; 有空格
l   print 要處理一串印出的字串,因此在串列語境下執行(在沒加圓括號時)
l   print 可選擇不加圓括號,有加的情況下則被當作函式呼叫,傳回值真或假
l   print (2+3)*4; 會印出5而非20,因為把圓括號當函式用了
l   printf %d 中間可加數字代表預設字元數,正為右推齊,負為左推齊
l   printf %% 為印出百分比符號
l   printf "Item List:\n" . ("%-10s\n" x @items), @items; 請思考這行程式
l   open filehandle_name, “開檔模式”, “filename”; 建立一個新的檔案代號
l   開檔模式字串為「<」表輸入;「>」表輸出(直接覆蓋原有的);「>>」以附加方式輸出
l   開檔模式後可接「:encoding(xxx)」指定編碼
l   close filehandle_name; 關閉檔案代號
l   die函式印出指定訊息,用標準錯誤串流處理,使程式以非零(錯誤)狀態立刻中止,若將「$!」放到錯誤訊息字串裡即代表錯誤訊息字串
l   務必檢查open的傳回值,0為成功;非0為失敗
l   use autodie; 自動幫你操作die函式
l   以「>」、「>>」開啟的filehandle可以在printprintf函式中使用
e.g.1 print LOG “content”;
e.g.2 printf STDOUT “content %d %s”, number, string;
l   filehandlelist content之間沒有逗號,因為filehandle是一個「間接受詞」
I gave Neo a book. or I gave a book to Neo. book是直接受詞;Neo是間接受詞
Program print filehandle contents. or Program print contents to filehandle.同理
l   由於預設輸出filehandle就是STDOUT,因此print/printf可以省略STDOUT,但可以使用「select」改變預設
l   資料輸出到filehandle預設是會經過緩衝區,可以將「$|」的值改為1即可立刻輸出資料
l   某個filehandle在存在的情況下要再次open,則Perl會自動幫你關閉舊有的filehandle,因此非特殊情況下不應該重新open Perl的六個標準filehandle
l   diewarn會自動將訊息送到STDERR
l   因此若想蒐集錯誤訊息的話,可以重新open一個STDERR到指定的檔案,在使用diewarn
l   STDINSTDOUTSTDERR任一個重新open失敗,Perl會自動找回原先的filehandle,前提是尚未改變特殊變數「$^F
l   say是一個在content結尾加上換行符號(\n)print,可搭配filehandle
l   filehandle存入純量變數中的優點:做為副常式的引數、可存入陣列或雜湊中、控制其作用範圍
l   filehandle存入的純量變數通常以「_fh」結尾,e.g. $filehandle_fh
l   printfilehandle後不可接逗號,會被當成串列內容

2012年10月19日

Binary Search in Perl

最近在學Perl...
外加大學資結都在鬼混所以去旁聽大學部的資檔結...

今天老師講到Binary Search
所以就用Perl自己來寫一個
然後就遇到了天大的小問題......

Perl算是動態語言所以變數都不需要型態宣告
且Perl一律使用「倍精度浮點數」,因此沒有整數值這東西
然後剛才在實作Binary Search的時候完全忽略這點的後果就是想不通Bug的原因

問題如下:
定義了一個串列為 3 7 14 20 23 32 41 44 56 57 73 89 93
然後開始用新學的語法開始劈哩啪啦的把Binary Search寫出來
很開心的要來測試一下,而且心裡想說怎麼可能會有Bug......
沒想到測到 7 這個數就發現竟然找不到它......
繼續測下去竟然有更多找不到的值,包括 20 56...
以為是演算法寫錯了,可是怎麼看都沒問題
最後把所有與演算法相關的變數($begin $end $mid)都印出來
才發現小數點的問題......

問題解決:
target begin end mid
7 0 12 6

0 5 2(2.5)

0 1(1.5) 0(0.75)

1(1.75) 1(1.5) 1(?)




20 0 12 6

0 5 2(2.5)

3(3.5) 5 4(4.25)

3(3.5) 3(3.25) 3(?)




56 0 12 6

7 12 9(9.5)

7 8(8.5) 7(7.75)

8(8.75) 8(8.5) 8(?)

從上面的表格可以看到幾個發生問題的值的搜尋過程
圓括號內的值是沒有把浮點數轉為整數的實際值
可以看出最後應該要找到結果的那邊begin都大於end
難怪while迴圈都會在此終止......

最後解法就是將 ($begin+$end)/2 以 int來轉型 → int (($begin+$end)/2)

以下程式碼:
use 5.012;

my @datas = qw( 3 7 14 20 23 32 41 44 56 57 73 89 93 );
my $locn =  &binary_search(56, @datas);
say $locn;

sub binary_search{
 my ($target, @list) = @_;
 my ($begin, $end, $mid) = (0, $#list, 0);

 while($begin <= $end){
  $mid = int(($begin + $end) / 2); #就是這行卡了我老半天...
  if($target > $list[$mid]){
   $begin = $mid + 1;
  }else{
   if($target < $list[$mid]){
    $end = $mid - 1;
   }else{
    return int($mid);
   }
  }
 }
 return -1;
}

Learning Perl 3


l   副常式就是一般所謂的函式,可重複利用的程式碼片段,不過在Perl裡通常是說使用者自訂的叫做「副常式」,內建的則是「函式」
l   副常式名稱規則與變數一樣,通常是開頭加上「&」,但有禁止的情況
l   副常式宣告:
sub name{
# your code
}
l   副常式呼叫:&name;
l   由於Perl所規定的副常式都具有傳回值,因此副常式的最後一次求值動作便當成傳回值,若最後一行為print則傳回1
l   在副常式名稱後面加上引數串列(argument list)就可傳值,此串列值將被記在「@_」這個陣列變數中,在此副常式結束前都存在
l   若全域變數有@_,也不用擔心副常式的參數陣列會有問題,副常式當中呼叫另一副常式也一樣,因為在自己的@_出現前會先將之前的存起來
l   Perl當中所有的變數預設都是「全域變數」
l   用「my」運算符建立副常式內部的語彙變數(lexical variable),因此這些變數作用範圍僅限於此副常式內,通常也用在區塊內e.g. if, while, foreach
l   因此可以習慣性以 my($x, $y, $z) = @_; 放於副常式開頭將變數指定給特定語彙變數
l   高水位標記:
sub max{
        my($max_so_far) = shift @_;
        foreach (@_){
                if($_ > $max_so_far){
                        $max_so_far = $_;
}
}
$max_so_far;
}
l   my($num) = @_; #串列語境;my $num = @_; #純量語境
l   my沒用圓括號就只能宣告一個變數,用圓括號才能宣告多個
l   use strict」放在程式開頭或任何想套用此規則的區塊內,便能強制使用一些良好的程式語言規則,或使用use 5.012便自動為你載入strict
l   字串比較只能用「eq」運算符而非「==
l   副常式除了回傳純量值亦可除回串列值
l   state代替my來宣告語彙變數,並且在兩次副常式呼叫之間保存變數的值