Posted on Leave a comment

單元測試 – 隔離框架Substitute.For

STUB的功能

這邊是NSubstitute的說明:http://nsubstitute.github.io/help/getting-started/
Substitute是.NET裡的一個隔離框架,若要使用,需要額外在測試專案用NUGET去安裝NSubstitute

1. 動態產生假物件
2. 模擬回傳值
3. 測試監聽事件
4. 驗證傳入參數是否正確

使用Subsitute(Sub)
使用方法如下
calculator = Substitute.For();
設定呼叫某個方法應該回傳的值
calculator.Add(1, 2).Returns(3);
Assert.That(calculator.Add(1, 2), Is.EqualTo(3));
下面可以驗證是否Add這個FUNC有被呼叫到
calculator.Add(1, 2);
calculator.Received().Add(1, 2);
calculator.DidNotReceive().Add(5, 7);
下面的程式能夠判別傳入的參數是不是正確
calculator.Add(10, -5);
calculator.Received().Add(10, Arg.Any());
calculator.Received().Add(10, Arg.Is(x => x < 0));[/code] 驗證回傳值是否正確 [code lang="C#"]calculator .Add(Arg.Any(), Arg.Any())
.Returns(x => (int)x[0] + (int)x[1]);
Assert.That(calculator.Add(5, 10), Is.EqualTo(15));

使用Substitute來針對不同狀況實作假介面

這是一個用MOCK方法的範例

using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//using Microsoft.VisualStudio.TestTools.UnitTesting;
using NUnit.Framework;
using RsaSecureToken;
using Assert = NUnit.Framework.Assert;

namespace RsaSecureToken.Tests
{
    [TestFixture]
    public class AuthenticationServiceTests
    {
        [Test()]
        public void IsValidTest()
        {
            var target = new AuthenticationService(new FakeProfile(), new FakeToken());

            var actual = target.IsValid("joey", "91000000");

            //always failed
            Assert.IsTrue(actual);
        }
    }

    public class FakeProfile:IProfile
    {

        public string GetPassword(string account)
        {
            if (account == "joey")
            {
                return "91";
            }

            throw new Exception();
        }
    }

    public class FakeToken:IRsaToken
    {
        public string GetRandom(string account)
        {
            return "000000";
        }
    }
}

上面做法有什麼問題?

  • 每一個不同的依賴案例就要製作一個不同的FakeObject,會讓寫測試的時間太久
  • 沒辦法直接從程式碼知道為什麼這樣會是Vaild

動態產生物件的使用完整範例

Subsitute.For()
定義假物件行為(stub)
fake.方法(參數).Returns(值)
[Test()]
public void IsValidTest()
{
var fakeProfile = Substitute.For();
fakeProfile.GetPassword(“joey”).Returns(“91”);

var fakeToken = Substitute.For();
fakeToken.GetRandom(“”).ReturnsForAnyArgs(“000000”);

var target = new AuthenticationService(fakeProfile, fakeToken);
var actual = target.IsValid(“joey”, “91000000”);

//always failed
Assert.IsTrue(actual);
}
}

驗證某個函數是否被呼叫

使用mock object assertion
需求:驗證是非法的時候要記一個log
fake.Receive(次數).方法(參數驗證)
不要用太多mock就算要使用要避免過度指定,也就是當prod code小小變動就導致測試程式壞掉

下面的程式碼可以驗證當呼叫SyncBookOrders()時是不是會呼叫Insert這個函數兩次:
[Test]
public void Test_SyncBookOrders_3_Orders_Only_2_book_order()
{
var result = new List
{
new Order
{
Type = “Book”
},
new Order
{
Type = “Book”
},
new Order
{
Type = “Item”
}
};

var target = new OrderServiceForTest();
target.SetOrder(result);

var fakeBookDao = Substitute.For();
target.SetDao(fakeBookDao);

target.SyncBookOrders();

fakeBookDao.Received(2).Insert(Arg.Is(m => m.Type == “Book”));
}

驗證傳入參數

驗證傳入的參數是否包含某些關鍵字
_logger.Received(1).Save(Arg.Is(m => m.Contains(“joey”) && m.Contains(“login failed”)));

Posted on Leave a comment

單元測試 – Code Coverage的意義

如何用Code Coverage來衡量單元測試的成果

  • 在build的時候要自動去跑測試(使用CI,CD當作工具)> 大於0%
  • 關心相對趨勢 大於 絕對數字,衡量相對的數字,評估code coverage的數值有沒有比昨天高,每一次針對PROD的程式碼修改都要加上測試,這樣這個數值只會往上不會往下。鼓勵持續的COMMIT > 持續整合,每天都要COMMIT個5~6次或7~8次。
  • 那些測試的投資報酬率高:為什麼要寫測試?目標是要提升產品品質。假設這段PROD CODE沒有BUG,其實不需要為它寫測試。那什麼東西的投資報酬率最高?
    • 1. 要修正的BUG(BUG越晚發現修正成本就更高)
    • 2. 實務上常跑到的scenario
    • 3. 最主要的情境
    • 4.和錢有關的
    • 5.和人命有關的(EX:自動駕駛系統)
    • 6. 最常改到的CODE,可以避免被改錯
  • 那code coverage的意義如下:
    • 觀察沒有被含蓋到的情境:判斷要不要補測試
    • Dead Code:根本就不會跑到的Code(代表這一段PROD CODE不會跑到)

最好導入的方式

從上面看來,兩個最好導入的方式:1. 針對所有BUG的修改去寫測試,2. 針對新的專案去寫測試
然後要確認code coverage不可以往下掉。
假如現在有一陀爛CODE,要增加新功能在那堆爛CODE裡,先把爛CODE抽成method,然後再抽成新的Class
針對爛CODE的public的情境先寫測試,會讓需要寫測試的範圍變小很多。

測試的品質

  • 測試的程式一定要重構
  • 測試的語意一定要清楚明白,一般Assert都會抽出成一個func,這是為了讓測試更容易理解,可以很簡單的的從測試的程式碼由語意就能理解這個測試要做什麼
  • 寫測試的難度會反應程式的好壞
Posted on Leave a comment

單元測試 – 使用Fake Object

抽取相依的物件並且覆寫

下面是一個範例,這個範例的SUT是Holiday.cs,如果今天是9/1就傳HappyBirthday,否則就傳No
因為單元測試應該要能夠具有隔離的特性,不可因為今天的日期不一樣而有不同的結果
所以”取得今天日期”,就會讓程式碼變得不可測試。

在下面的範例裡,我們可以看見如何使用假物件去測試不可測試的程式碼:
Holiday.cs
using System;

namespace TestProject1
{
public class Holiday
{
public string IsTodayJoeyBirthday()
{
var date = GetToday();
return (date.Month == 9 && date.Day == 1) ? “HappyBirthday” : “No”;
}
//將有相依的部份抽出來
protected virtual DateTime GetToday()
{
return DateTime.Today;
}
}
}
Test2Cs.cs
using System;
using NUnit.Framework;
using TestProject1;

namespace TestProject1
{
[TestFixture]
public class Test2Cs
{
[Test]
public void today_is_not_joey_birthday()
{
var target = new HolidayForTest();
target.SetToday(new DateTime(2015,1,1));
Assert.AreEqual(target.IsTodayJoeyBirthday(), “No”);
}

[Test]
public void today_is_joey_birthday()
{
var target = new HolidayForTest();
target.SetToday(new DateTime(2015,9,1));
Assert.AreEqual(target.IsTodayJoeyBirthday(), “HappyBirthday”);
}
}
}
//創建一個假物件並且繼承Holiday,替換掉取得今日日期這件事
internal class HolidayForTest : Holiday
{
private DateTime _today;
protected override DateTime GetToday()
{
return _today;
}

public void SetToday(DateTime date)
{
_today = date;
}
}

如何對現行沒有測試的PROD CODE加上unit test


這一個測試的重點在於:針對依賴的code,找出不可控制的地方並抽出來

  • 找到不可控制的依賴
  • 抽出方法
  • 把private改成protect virtual
  • 在測試專案新增子類繼承SUT
  • override protected方法並加開set方法
  • 測試SUT改測子類
  • set依賴的值

使用依賴注入

使用依賴注入來達成低藕合高內聚的程式碼
1. 針對相依的物件抽出interface,針對相依的值抽出field
2. 產生contructor,選要注入的field去依賴注入
3. 產生無參數的constructor,確保本來的程式無誤
4. 測試程式新增fake物件做interface,決定行為並注入SUT

所謂低藕合高內聚的程式碼也可以讓程式更容易被測試

用Fake Object來取代相依物件的方法

如果測試的資料都是固定的,可以在測試裡面創建假物件來注入SUT(測試目標)

以下為範例程式
AuthenticationServiceTests.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
//using Microsoft.VisualStudio.TestTools.UnitTesting;
using NUnit.Framework;
using RsaSecureToken;
using Assert = NUnit.Framework.Assert;

namespace RsaSecureToken.Tests
{
[TestFixture]
public class AuthenticationServiceTests
{
[Test()]
public void IsValidTest()
{
var target = new AuthenticationService(new FakeProfile(), new FakeToken());

var actual = target.IsValid(“joey”, “91000000”);

//always failed
Assert.IsTrue(actual);
}
}

public class FakeProfile:IProfile
{

public string GetPassword(string account)
{
if (account == “joey”)
{
return “91”;
}

throw new Exception();
}
}

public class FakeToken:IRsaToken
{
public string GetRandom(string account)
{
return “000000”;
}
}
}
AuthenticationService.cs
using System;
using System.Collections.Generic;

namespace RsaSecureToken
{
public class AuthenticationService
{
private IProfile _profileDao;
private IRsaToken _rsaToken;

public AuthenticationService()
{
_profileDao = new ProfileDao();
_rsaToken = new RsaTokenDao();
}
//for test
public AuthenticationService(IProfile profile, IRsaToken rsaToken)
{
_profileDao = profile;
_rsaToken = rsaToken;
}

public bool IsValid(string account, string password)
{
// 根據 account 取得自訂密碼
var passwordFromDao = _profileDao.GetPassword(account);

// 根據 account 取得 RSA token 目前的亂數
var randomCode = _rsaToken.GetRandom(account);

// 驗證傳入的 password 是否等於自訂密碼 + RSA token亂數
var validPassword = passwordFromDao + randomCode;
var isValid = password == validPassword;

if (isValid)
{
return true;
}
else
{
return false;
}
}
}

public interface IProfile
{
string GetPassword(string account);
}

public class ProfileDao : IProfile
{
public string GetPassword(string account)
{
return Context.GetPassword(account);
}
}

public static class Context
{
public static Dictionary profiles;

static Context()
{
profiles = new Dictionary();
profiles.Add(“joey”, “91”);
profiles.Add(“mei”, “99”);
}

public static string GetPassword(string key)
{
return profiles[key];
}
}

public interface IRsaToken
{
string GetRandom(string account);
}

public class RsaTokenDao : IRsaToken
{
public string GetRandom(string account)
{
var seed = new Random((int)DateTime.Now.Ticks & 0x0000FFFF);
var result = seed.Next(0, 999999).ToString(“000000”);
Console.WriteLine(“randomCode:{0}”, result);

return result;
}
}
}

Posted on Leave a comment

單元測試 – 寫測試的基本原則

熱鍵

  • Alt+Enter: Quick+Action
  • Ctrl+R,M 抽方法出來
  • code snippets / live template
  • constructor: ctor
  • property:prop
  • console.writeLine)():cw
  • ctrl+R,F: Extract Field
  • Alt+Insert
  • 循環剪貼簿:ctrl+shift+V

測試替身有下面三種

  • stub:不做驗證,單純只做模擬相依物件的行為
  • mock:一開始就要把所有的都定義清楚,應該要呼叫那個方法,所有的值一定要一模一樣,否則就會報錯(嚴格,敏感,不穩定)
  • spy: 則是把所有的互動先做完,只驗要驗的,剩的沒有測就是都算過,差異是一個從嚴一個從寬。(寬鬆)

因此mock和spy本身含有驗證(Assertion),而stub本身只有在模擬相依的物件而已。

比較物件屬性的方式

比較兩邊的物件包含子物件完全相同

expected.ToExpectedObject().ShouldEqual(actual);

比較以expected為主的屬性去比較相對應的actual是否相同

expected.ToExpectedObject().ShouldMatch(actual);

測試範例如下
namespace AssertionSample
{
[TestFixture]
public class AssertionSample
{
private CustomerRepo customerRepo = new CustomerRepo();

//比較物件屬性的方式
[Test]
public void CompareCustomer()
{
var actual = customerRepo.Get();
var expected = new Customer
{
Id = 2,
Age = 18,
Birthday = new DateTime(1990, 1, 26)
};

//CollectionAssert也是在比較物件的位置,所以也會有相同的問題
expected.ToExpectedObject().ShouldEqual(actual);
}

//比較集合的方式
[Test]
public void CompareCustomerList()
{
var actual = customerRepo.GetAll();
var expected = new List
{
new Customer()
{
Id = 3,
Age = 20,
Birthday = new DateTime(1993,1,2)
},
new Customer()
{
Id = 4,
Age = 21,
Birthday = new DateTime(1993,1,3)
}
};

expected.ToExpectedObject().ShouldEqual(actual);
}

//組合式物件的比較
[Test]
public void CompareComposedCustomer()
{
var actual = customerRepo.GetComposedCustomer();

var expected = new Customer()
{
Age = 30,
Id = 11,
Birthday = new DateTime(1999, 9, 9),
Order = new Order {Id = 19, Price = 91},
};

expected.ToExpectedObject().ShouldEqual(actual);
}

//回傳的資料的PROPERITY很多,但是我們只想比其中幾項
[Test]
public void PartialCompare_Customer_Birthday_And_Order_Price()
{
var actual = customerRepo.GetComposedCustomer();

//有自定型別的一定要改成匿名型別
var expected = new
{
Birthday = new DateTime(1999, 9, 9),
Order = new {Price = 91},
};

//使用匿名型別 去比較以expected為主去比較相對應的actual是否相同
expected.ToExpectedObject().ShouldMatch(actual);
}
}

寫測試的規則


測試程式不含商業邏輯,所有的測試都應該是直述句
不應包含以下的元素:

  • prod business logic
  • 不含if, else, switch case等邏輯程式碼
  • 更不含try..catch
  • 不含for, while, foreach, do..while

善用assertion package

  • C#: expectedObjects,FluentAssertions
  • Java: Assert J

不要攤開properity做比較

[Test]
public void Divide_Zero()
{
var calculator = new Calculator();
var actual = calculator.Divide(5, 0);
Action action = () =>
{
calculator.Divide(5, 0);
};
action.Should().Throw();
//never use try/catch in unit test
}

寫測試一定要重構,不然在測試需求異動時和寫測試時會太花時間
因此不應該要有太多不會用到的資訊
讓測試的目標的意圖可以很明顯
要如何加快單元測試撰寫的速度很重要,這樣才有可能可以實踐單元測試
沒有時間是個問題,但是我們要去面對如何解決這個問題
要知道怎麼用工具怎麼寫比較快

Posted on Leave a comment

單元測試的藝術-單元測試基礎

單元測試

一個單元代表的是系統中的工作單元或是一個使用案例
被測試的系統(System Under Test)我們稱做SUT或者Class Unit Test(CUT)
一個單元測試是一段程式呼叫一個工作單元,並驗證工作單元的一個具體最終結果。如果對這個最終結果的假設是錯誤的,那單元測試就失敗了。一個單元測試的範圍,可以小到一個方法,大到多個類別。

優秀單元測試的特質

  • 自動化,可被重覆執行的
  • 很容易被實現
  • 非臨時性的
  • 任何人都可以按鈕執行他
  • 執行速度快
  • 執行結果每次都是一致的
  • 能完全掌控被測試的單元
  • 完全被隔離的
  • 若執行失敗會有清楚的原因

整合測試

整合測試是一個有順序的測試過程,將軟硬體相結合並進行測試直到整個系統被整合在一起。也就是這個測試對被測試的單元並沒有完全的控制,而是使用該單元一個或多個真實依賴的相依物件,例如:時間,網路,資料庫,執行緒,亂數產生器等等。

一個單元測試通常包含了三個行為

  • 準備(Arrange):物件,建立物件,進行必要的設定
  • 操作(Act): 物件
  • 驗證(Asset): 某件事符合預期

Assert類別

  • Assert.True: 驗證一個布林條件,見Assert.False
  • Assert.AreEqual: 驗證傳回的值應相同
  • Assert.AreSame: 驗證兩個參數應指向同一個物件

使用參數來測試

使用TestCase標籤

Setup和tesrdown

Posted on Leave a comment

鳳凰專案:看IT部門如何讓公司從谷底翻身的傳奇故事

這本書算是一本故事書,也因為是以故事的型態去描述一間公司如何從谷底翻身,讀者會更能夠明白『管理』對於一間公司有多麼重要。
也能從故事裡面直接理解書中所闡述的管理方式在實際情況是怎樣的去運用,在運用時也不會是直接的一帆風順,而是有重重的考驗並且需要高層的全力支持與理解。
我覺得此本書是管理相關書籍中相當值得一看的書。

因為故事情節用簡述的,就沒辦法讓讀者感受書中情境,因此本文主要為筆者自己讀後的筆記
主要紀錄我認為書中很重要的管理方法和管理思維,書內的附錄也有此書管理思維的整理。

有關故事內容,想了解的話建議自己去買書來看。

Work in process控制

在這邊他們使用工廠管理來比喻我們在開發專案的狀況。

對工廠管理而言,在生產線會盡量的去避免讓工廠內同時有過多的在製品,也就是所謂庫存。
對開發專案來說,在製品指的是開發中但是尚未開發完成的功能,還無法帶給公司收益,也是要極力去減少的。

在生產線上,一定會有處理效率最差的點,我們稱為瓶頸點(或稱約束點),創造約束理論的艾利高德拉特告訴我們,在瓶頸以外任何地方做的改進都是假像
的確,在瓶頸點之後做的任何改進都是徒勞無功的,因為只能乾等瓶頸把工作送過來,而在瓶頸點之前做的任何改進只會導致瓶頸處堆積更多庫存。

因此,在開發專案時,要了解公司的瓶頸點在那,是什麼地方讓最多的專案在等待這個資源而讓需求被卡住無法繼續進行。
以鳳凰專案而言,因為過多的核心資訊被掌握在一個天才員工布倫特手中,只有他知道怎麼處理,導至這個員工每天都必須處理非常多的事也有非常多的事在等待這一個資源。

下圖表示當一個越忙錄的資源,其他資源想等待這個資源的時間會越來越高

因此改善這個狀況,第一步就是要先釐清約束點所在,第二步就是充分利用約束點

以此書案例來說,約束點是天才員工布倫特,那他們首先做的就是保護這個約束點,不讓大家直接指使這個約束點去做他們認為重要的事,他們安排了一位專門的人員來安排這位約束點的工作內容,避免大家一直想私自去使喚他做事。接著他們要讓約束點的時間不被浪費,確認約束點永遠不會因為要遷就其他資源而枯等。再來他們將布倫特的工作標準化,讓更多其他的人也可以去接手做布倫特正在做的事,也就是提升約束點的產出。

總之,我們要記得任何對非約束點的改善都只是鏡花水月

三步工作法

  • 第一步工作法

    幫助我們理解如何建立快速的工作流,讓工作順暢地從開發部移動到IT運維部,因為那正是公司與客戶之間的銜接。

    這邊需要去了解公司的價值點,相較於把更多的工作投入系統,將不需要的工作從系統中剃除甚至更為重要,讓資訊部門能夠很快速的產出最精簡可用的有價值的系統,並且獲得反饋。為此,我們需要知道,與達成企業目標息息相關的東西是什麼,不論是專案,運營,戰略,合規,安全性等,通通有可能(請見下方的訂定組織目標)。

  • 第二步工作法

    告訴我們如何縮短及增強回饋循環,因而能夠從源頭開始解決品質問題,並且避免重工。在這邊我們也必須設法去根除計劃外工作的最大來源。

    所有計劃中的工作,在執行工作前,必須要分門別類地列出完成工作所需的一切先決條件:例如說,筆電型號,使用者資訊的規格,軟體及需要的授權,以及他們的組態,版本資訊,資安要求,處理能力和連續性需求等。

    也就是建構資源清單,亦是物料清單,以及需要的工作中心與生產途程,一旦備妥,加上工單和你的資源,就可以釐清產能及需求為何,並弄清楚可否接受為新的工作,並實際為它進行排程。

    第二步工作法的關鍵部份是以視覺化的方式呈現等待時間,那樣就能知道你的工作正在某人的佇列中排隊好幾天,或者是工作必須往後退,因為未備妥完整的零件。藉由需求清單被列出,也可以讓接受需求的人在接受訂單時,可以先確認每一個必須參與的資源都能夠有需要的投入,讓需求,銷售,開發能夠一起擬定生產計劃。

  • 第三步工作法
    告訴我們如何建立一種文化,既能鼓勵大家探索,從失敗中汲取教訓,又能理解反覆與練習是精通工作的先決條件。

    提升預防性工作是全面生產維護(Productive Maintenance, TPM)這類計劃的核心,精實社群(Lean Community)信奉全面生產維護的精神,主張我們應不惜一切代價提升維護水準。『改善日常工作甚至比進行日常工作更重要』。持續給系統施加壓力,從而不斷強化習慣並且改善某件事情。

    也就是所謂『改善型(Improvement Kata)』,書中是使用為期兩周的改善循環,每個改善循環都要實施一個小型的計畫》執行》查核》行動的專案,持續朝目標邁進。

    在此必要的實務作為包括:建立創新,勇於冒險及高度信任的團隊文化。把至少20%的開發和IT運維週期分配給非功能性需求,並持續強化及鼓勵大家進行改善活動

四種工作類型

在這邊這本書將工作份為四種類型:

  • 業務專案:公司所有的正式專案
  • 內部IT專案:由業務專案衍生出來的基礎架構或IT運維專案,以及內部生成的改善專案(如建立新環境和部署自動化)。這些專案經常未被集中管理,而是隸屬於預算所有者。
  • 變更:包括需求變更或BUG等
  • 計劃外工作或救火工作:包括production issue,通常由上面三類工作導致,而且往往以犧牲其他計畫內工作為代價。在書中說這類型的工作是最具有破壞性的,它並非實質的工作,其他三種工作都是基於需求而事先計劃好的。計劃外的工作會阻止我們進行其他三類的工作,就像物質和反物質,在計畫外的工作面前,所有計劃內的工作都會被延後。
    另外一點則是未被償還的技術債,源自於走捷徑,短時間內,那樣或許行的通,但是就像金融債一樣,久而久之,利息越滾越多。如果一個組織沒有還清它的技術債,那麼,就必須一點一滴,耗費心力,以計劃外的工作的形式來償還那些技術債的衍生利息

訂定組織目標

在這本書有列出了CFO的營收目標:

  • 公司體質健全
  • 營收
  • 市占率
  • 平均訂單規模
  • 盈利能力
  • 資產收益率
  • 財務狀況
  • 訂單轉化成現金的周期
  • 應收帳款
  • 準確且及時的財務報告
  • 借貸成本

在本書他們針對每一個營收目標列出『倚賴IT的區域』及『IT導致的業務風險』以及『倚賴的IT控制』來列出IT在這些目標項目中的角色。倚賴的IT控制指的是防範這些錯誤發生的反制措施,或者至少能夠偵測到問題及做出回應。

在這本書很戲劇化的是SOX-404的稽核專案,資安部門原本一直要求開發部門開發一個安全控制的系統來應付發生錯誤時的資安問題,最後才發現檢測重大錯誤所倚靠的控制手段是人工對帳步驟,而不是上游的IT系統。其實財務部門早已用人工的方式來達到這個資安的要求了,不需額外再開發相關的系統。

因此,不同部門間的資訊透明度真的是一件很重要的事。

遠程目標則包括:

  • 我們具競爭力嗎?
  • 了解客戶的需求和期望:我們知道要創造什麼嗎?
  • 產品組合:我們有正確的產品嗎?
  • 研發效能:我們能夠有效地建立產品嗎?
  • 產品上市時間:我們能夠盡快把產品推向市場,應且搶占一席之地嗎?
  • 銷售管道:我們的產品能夠觸及感興趣的潛在客戶嗎?
  • 我們的作法有效嗎?
  • 按時交貨:我們有尊守對客戶的承諾嗎?
  • 顧客維繫:我們正在增加客戶還是流失客戶?
  • 銷售預測準確率:我們可以把銷售預測準確率納入銷售計畫流程嗎?

相關連結

Posted on Leave a comment

Agile LEGO City workshop

這是由公司同事Jed所發起的公司內部課程。
Agile LEGO City workshop是用樂高積木來模擬敏捷開發的實際情形的一個小遊戲,藉著遊戲可以從中體認到軟體開發時,可能會遇到的許多狀況,並藉由會後的討論與檢討來讓參與者更能夠理解敏捷開發的意義。

敏捷開發宣言

在一開始時,我們先讀了下面的敏捷開發宣言

藉著親自並協助他人進行軟體開發,
我們正致力於發掘更優良的軟體開發方法。
透過這樣的努力,我們已建立以下價值觀:

個人與互動 重於 流程與工具
可用的軟體 重於 詳盡的文件
與客戶合作 重於 合約協商 
 回應變化 重於 遵循計劃 

也就是說,雖然右側項目有其價值,
但我們更重視左側項目。

讓參與者能夠從中理解敏捷的核心精神,也就是重視團隊間的溝通互動,接受需求的變化
並且讓我們知道敏捷開發最核心的就是這個宣言裡所描述的,所有敏捷實作方法如SCRUM等都需要建構於這個核心精神上。

遊戲規則

接著就是介紹LEGO遊戲的規則:
1. 依照下圖建造客戶所需的樂高城市,項目越前面代表客戶越重視的

2. 這個建城市的PO就是主講人Jed,在開始之前可以先和Jed提問,在每一個sprint的結束也會由PO來做驗收
3. 總共會有8個sprint,每個sprint開始之前要先計劃好這個sprint要做的,並移到TO DO去
PS:在這邊使用如下圖的看板方法去追每一個TASK的進度

4. 有一個計分表去計算每個sprint計劃要做的和實際驗收成功的任務比較表

遊戲進行

在開始遊戲後,本組所交付的任務頻頻被打槍,首先是我們在第一個sprint交付了平房,結果被打槍~原因是,請依照客戶需求優先順序做交付,我們前面都還沒有交付,不接受。
接著河濱公園安全設施因為圍牆沒蓋滿被拒絕,然後開始沒有做門,門沒有面對道路,沒有畫道路,圓環要在市中心,托兒園應臨近住宅….
等等原因一直被拒絕交付退回重改(火大)。

這是我們小組最後所交付的成果圖

會後檢討

我覺得這部份是這整個遊戲中最精華的部份,很感謝Jed還特別為我和Jack再做一次報告,讓我得以有機會聽到這個最精華的部份。

首先他讓我們提出我們覺得在遊戲中的感想,最多人提出的就是我們在一開始沒有搞清楚客戶需求,導致頻頻被退件
以及在一開始沒有先對整個城市做完整的規劃,例如圓環要在市中心等等…
造成我們所做的產品一直無法即時被release

然後Jed分享了敏捷開發的原則

The Agile Principles (敏捷原則)

1. 最為優先的事情是透過早期與持續交付有價值的軟體來使客戶滿意。
有價值的軟體指的是要重視客戶的優先順序,交付對客戶最有價值的東西
2. 歡迎需求的變動,即使是在開發的晚期。敏捷式流程駕馭變動來作為客戶的競爭優勢。
重點在於掌握變更,歡迎改動
3. 頻繁的交付工作產生的軟體,自數週至數月,週期越短越好。(2~4周)
4. 領域專家與開發成員必須一同作業,並貫穿整個專案開發時期。團隊的概念
5. 使用積極的工作成員來建構專案,給予他們環境以及支援所需的一切,然後信任他們能夠完成工作。
支援積極的個人(指願意主動多做更多事的人)帶動團隊氣氛,也就是信任團隊成員
6. 在開發團隊中最快也最有效的傳遞資訊方法就是面對面的溝通。
7. 工作產生的軟體是衡量進度最主要的依據。
這是最主要度量進度的方法,對使用者來說一定要完成的部份,因此找到使用者需求的核心價值十分重要
8. 敏捷式流程倡導水平一致的軟體開發
持續的開發,維持穩定步調,讓團隊狀況保持穩定
9. 專案發起者,開發人員以及使用者都必須持續的維持專案進度。
精簡,避免over design,不做太多額外的設計
10.持續重視技術的優勢以及設計品質
追求優良設計與技術
11.最好的架構、需求以及設計會出現在能夠自我管理的團隊裡
可以不需要PM就可以完成架構需求與規劃
12.在規律的反覆之間,團隊會反省與思考如何更有效率,然後相對的來調整與修正團隊的開發方式。
以天為單位定時自省,適當調整與修正自己的行為

在上述的精神底下的意義主要就是承諾,責任與互相信任。
敏捷是一個價值觀,也就是敏捷宣言與原則所述說的這樣的價值觀,Scrum是敏捷開發的一個框架,在Scurm裡所說的站立會議是一個scrum的儀式
優文分享:我對Daily Scrum的理解與看法
一般來說敏捷開發只估算這一回合要做的事情,過去我們在估算專案講的是人/天,這是以成本的角度去切入
而敏捷開發主要講的是故事點,也就是客戶需求的核心價值,重心在於價值。

對敏捷開發而言,估算是為了針對團隊生產力訂出並製定策略,重視的是產品的核心價值
因此可以看成是這樣

因此對於敏捷開發來說,每一個sprint只規劃該sprint要做的事,將客戶所需的功能切分到最小,每2-4週交付一次,每一次的sprint會將所有任務填上相對的一個數字,由多個成員來估算所需工時,並取一個平均值(若有差異較大的則需確認原因)
並在該sprint去計算本來我們預估這個sprint可以完成幾個工時,但實際完成了幾個?
再使用這個實際完成的數字去調整下一次sprint要release的總工時。
在這樣個估算方式下,隨著團隊穩定成長與默契的增加,估算的準確度會慢慢提升,如下圖

也因此在敏捷開發會特別重視團隊的穩定,惟有穩定的團隊才能帶來穩定的產品開發。

參考資料

雖然本文實際上沒有參考到這些文章,不過我去網路上爬了一些別人做LEGO workshop的經驗分享的文章
感覺滿有趣的,因此也在此附上

Posted on

Intro to Artificial Intelligence

課程網址:https://www.udacity.com/course/viewer#!/c-cs271

什麼是Intelligent Agent

人工智能代理(Agent)根據環境的感知器(下圖的Sensors)傳入的資料,經過一連串的映設函數(紅箭頭部份),藉由效應器對環境做影響(下圖Actuators)。在裡面紅色箭頭部份是最重要的課題,這稱為代理控制策略。
螢幕快照 2014-02-18 下午11.05.25
如上圖這樣的一個決策過程會重覆進行很多次,從環境將資訊透過Sensors傳給Agent,經過代理做出決定,然後用Actuators影響環境,這樣的一個周期稱為perception action cycle。

AI應用範疇

  1. 財務金融:應用環境可以在股票市場、債卷市場或期貨市場,感知到一些東西的資訊和相關新聞,決策則是買入和賣出。
  2. 機器人學:這個是研究時間最長的一門ai領域,他的Sensors及Actuators都是與物理環境交互,而更為特殊。
  3. 遊戲:分兩種,一種是與人類對戰,像是象棋對戰,對Agent來說玩家是環境,它必需觀察玩家的動作,目的是戰勝玩家。另一種是遊戲情境模擬,為了要讓遊戲更自然,讓玩家感覺遊戲世界像真實世界般。
  4. 醫藥學:可以用在終身醫療上,輸入過去病史,讓它可以幫助判斷未來可能出現的疾病。
  5. WEB應用:像智能搜索,對語意進行判定,然後幫助找出可能更符合使用者想要的搜尋結果。

術語介紹

  1. 完全可觀察的環境 vs 部份可觀察的環境(partially)
    完全可觀察:例如一些牌類遊戲,當所有牌都已被掀開在桌子上,我們可以看到所有的資訊,可以算出最佳解答。
    部份可觀察:像另一些紙牌遊戲,有些牌是掀開的,有些則是未被掀開的。這時就要去觀察並記憶過去曾有的牌型紀錄,和桌上現有的牌,來推算可能是最有利的出牌方式。越多的歷史紀錄可以幫助Agent去做出更正確的推斷。這個循環稱為馬爾可夫模型(Markov Model),我們會廣泛的討論要如何具有下面的這種結構:
    螢幕快照 2014-02-19 上午1.10.56拷貝
  2. 確定性環境 vs 隨機性環境(stochastic)
    確定性環境:Agent所做的結果會產生的影響是否確定。例如像在象棋遊戲中,移動一個棋子的效果是完全可預見的。
    隨機性環境:像是大富翁的擲骰子遊戲,雖然所做的決定會影響結果,但是我們無法預期骰子會擲出多少,因此無法完全預見所做決策會影響到的效果。
  3. 離散環境 vs 連續環境(continuous)
    離散環境:有有限多個選擇和可能性,例如象棋遊戲。
    環境:可做的行動或可能性是無限的,例如丟飛鏢會有無限種角度的可能性與加速方式
  4. 良性環境 vs 敵對環境(adversarial):環境因素的存在與影響是否剛好與我們所要達成的目標相反?

AI的不確定性

螢幕快照 2014-02-19 上午1.51.16

包括感知器的有限、很多時候會忽略掉某些不重要或無法判別的因素、敵對因素的影響、隨機性環境的不可預測因素、可計算的步數有限性等…。而AI的存在就是在處理並控制不確定性以供做出決策的規則。

定義問題

  1. 初始狀態:一開始的狀態
  2. 動作狀態:若當Agent處於該狀態時會有那幾種可能性(返回一組該狀態可做行動的序列)
  3. 結果狀態:一個狀態和一個動作為輸入,一個新的狀態為輸出
  4. 目標測試函數:是否這個狀態是可達成目標的。
  5. 路徑成本函數:計算從a狀態到b狀態要花的成本

幾個路徑計算方式

螢幕快照 2014-02-19 下午2.20.59
求從上圖的路徑中的A點到B點的最佳路徑的方式有下面這些工具:

  1. Graph Search vs Tree Search
  2. Graph
  3. Tree
  4. BFS
  5. DFS
  6. Best-first Search:在大部份的時後,找到最佳解是最困難的,因此這部份很難實作,有許多文章有在探討相關實作方式。

像上述的這些搜尋方式,是完全不考慮有什麼意外發生的狀態下才有效的。
要使用上面這種路徑搜尋方式,環境必須符合下面幾項條件:
螢幕快照 2014-02-19 下午2.12.33

  1. 搜索域必須是完全可觀察的,換句話說,我們必須具備觀察我們開始的初始狀態的能力
  2. 域必須是可知的,我們必需知道我們可採取的行動的集合
  3. 必須為離散域,可選擇的行動的數量必需是有限的
  4. 域必須是具有確定性的,必須知道採取某一行動所產生的結果
  5. 域必須是靜態的,除了我們自己的行動以外,域中不可以存在任何可以改變它的東西

在AI中以機率去找出最佳解

貝葉斯概率:http://wiki.mbalib.com/zh-tw/%E8%B4%9D%E5%8F%B6%E6%96%AF%E6%A6%82%E7%8E%87
聯合概率分布:http://zh.wikipedia.org/wiki/%E8%81%94%E5%90%88%E5%88%86%E5%B8%83
貝葉斯網絡可以簡潔地表示一個聯合概率分佈狀況,如下圖可表示一個汽車壞掉可能原因的貝葉斯網路圖,我們觀察一些面板數值,然後去推斷可能的原因的發生機率,再去做交叉比對,這樣就可以列出一個概率分布圖。
螢幕快照 2014-02-19 上午3.15.19

機率學相關概念:互補事件獨立性事件(任意兩個變量的聯合概率是兩個機率的乘積)、貝葉斯法則

貝葉斯公式

螢幕快照 2014-02-19 下午7.55.50
P(A|B) = P(B|A) P(A) / P(B)
P(A,B) = P(A|B) P(B)

經過如下的推導
未命名1
可得知P(A,B|C) = P(A|B,C) P(B|C)

這邊有相關的理論說明:條件機率與貝氏定理

範例題目
螢幕快照 2014-02-19 下午5.37.10

是在探討今天若心情很好P(H)可能原因有天氣好P(S)或是被加薪P(R)
假使天氣好P(S)的機率為0.7
被加薪P(R)的機率為0.01

P(H | S,R) = 1 >> 天氣好且被加薪,心情好機率為1
P(H | ^S,R) = 0.9 >> 天氣不好但被加薪,心情好的機率為0.9
P(H | S,^R) = 0.7 >> 天氣好但沒被加薪,心情好的機率為0.7
P(H | ^S,^R) = 0.1 >> 天氣不好又沒被加薪,心情好的機率為0.1

則求出P( R | H,S)  > 也就是求出在心情好且天氣好的狀況下,是被加薪的機率
下面是好心人在討論版所給的詳解方式

According to Bayes formula p(R|H,S) = p(H,S|R)p(R)/p(H,S) = p(H|S,R)p(S|R)p(R)/p(H,S) (1)
In the second equality we used the analog of formula p(H,S) = p(H|S)p(S) but under definite value of R: p(H,S|R) = p(H|S,R)p(S|R).
Further, p(S|R) = p(R|S)p(S)/p(R). Plugging it in the formula (1) and accounting that p(H,S) = p(H|S)p(S) we get the targeted expression:
p(R|H,S) = p(H|R,S)p(R|S)/p(H|S). It’s the strict formula and should be remembered. It’s easy if you note that this formula is the custom Bayes rule p(R|H) = p(H|R)p(R)/p(H) but under definite value of S of all probabilities in the formula.

Posted on

Git版本控管學習筆記(1)

我過去一直都是使用SVN來做專案的版本控管的工作,
最近突然很頻繁的聽到圈內的人都用git這個東西,強調他分散式適合分散式開發等等的優點。
再加上最近有看到保哥的30天精通Git版本控管的教學文,
就決定好好的來學習一下這個新的版本控管系統。

在過去,較有名的檔案管理系統有SVNCVSVisual SourceSafeVSTS
不過,這些版本管理系統大都採用集中式控制,
也就是一定要連上Server才可以commit資料。

而Git則強調分散式管理,他可以在本機就先commit資料,當連到網路之後,才去與server上的系統做合併
這樣有一個很大的好處,就是即使今天人在國外,無法連上網路而需要繼續工作,
或公司的svn是使用區網去連接,但我們卻無法連接至公司網路時,
也可以commit程式的變更,這個特性在分散式的開發上十分的方便。

決定要學GIT後,第一個就是要選擇工具

CloudHsu推薦下面兩款:

  1. mac系統上使用SourceTree
  2. 在windows上使用gitextensions

上面這兩款都是圖形化GUI介面的操作軟體。

不過保哥在文章裡面有提到,最終在使用上,因為操作方便性以及使用方便性,還是會選擇使用GUI介面的操作工具。
但是,在剛開始學習時,由於對整個GIT概念的了解,他還是建議從下指令碼開始學習,以下是節錄他的學習建議:

* 先擁有 Git 基礎觀念,透過下指令的方式學習是最快的方式,不要跳過這一段
* 找多一點人跟你一起學 Git 版本控管,最好能直接用在實務的開發工作上
* 團隊中最好要有幾個先遣部隊,可以多學一點 Git 觀念,好分享給其他人,或有人卡關時,能適時提供協助
* 了解 Git 屬於「分散式版本控管」,每個人都有一份完整的儲存庫(Repository),所以必須經常合併檔案
* 使用 Git 的時候,分支與合併是常態,但只要有合併,就會有衝突,要學會如何解決衝突

因此我還是先裝Git for Windows,在保哥的系列文章的第二篇,有很詳細的解說
請看在Windows平台必裝的三套Git工具,一定要先裝Git for Windows才能裝GitHub for Windows
他一樣很大推SourceTree,這一套CloudHsu也說很好用!

首次使用如果有成功登入 GitHub 帳戶,GitHub for Windows 會自動建立一組 SSH Key-Pair

GitHub for Windows 幫你產生的 SSH Key 預設路徑如下:
"C:\Users\<username>\.ssh\github_rsa"
"C:\Users\<username>\.ssh\github_rsa.pub"

這樣以後可以不用每次登入GitHub都要輸入帳號密碼。

而預設的工作目錄則是在
C:\Users\<username>\Documents\GitHub

在登入github網站後,點選右上的Account settings,然後點選SSH Keys
可以看到現在連接到你gitHub帳號的電腦所使用的SSH key。

2013-11-11_180939
然後這是安裝好的GitHub for Windows介面的樣子
2013-11-11_183846

今天學的三個指令
<詳細教學文請見此>

  1. 建立本地儲存庫
    mkdir demo
    cd demo
    git init
  2. 建立本地共用儲存庫
    mkdir demo
    cd demo
    git init --bare
  3. 從遠端取出儲存庫
    (2) 先在此取得儲存庫的網址
    2013-11-11_182141
    (2) git clone [REPOSITORY_URI]

相關資料:

  1. 30天精通Git版本控管
  2. Git Magic
  3. Git教學
Posted on

利用ant 構建和部署專案

Ant 可以代替使用 javac 、 java 和 jar 等命令來執行 java 操作,從而達到輕松的構建和部署 JAVA 項目的目的。

1. 利用ant 的javac命令來編譯JAVA程式

Ant 的javac命令用於實現編譯JAVA 程式的功能。下面來看一個簡單的例子:首先我們建立名為 JAVATestPro的JAVA項目,建立src目錄為源代碼目錄,在src目錄下建立HelloWorld.java這個類檔。該類檔的內容如下:

public class HelloWorld {
    public static void main(String[] args) {
       System.out.println("hello world!");
    }
}

同時在JAVATestPro項目的根目錄下建立build.xml 檔,在該檔中編譯sr 目錄下的JAVA檔,並將編譯後的class檔放入build/classes 目錄中,整個項目的目錄結構如下:

|JAVATestPro
|src
|build
|classes
|build.xml

在編譯前,需清除classes 目錄,該檔的內容如下:

<?xml version="1.0" ?>

<project name ="javacTest" default="compile" basedir=".">
<target name="clean">
<delete dir="${basedir}/build"/>
</target>
<target name="compile" depends ="clean">
<mkdir dir ="${basedir}/build/classes"/>
<javac srcdir ="${basedir}/src" destdir ="${basedir}/build/classes"/>
</target>
</project>

在項目根目錄(C:\ThinkInJAVACode\JAVATestPro)執行ant命令後,可在該目錄下發現新生成的build/classes子目錄,編譯後生成的HelloWorld.class檔就在該目錄下。

2. 使用java命令執行JAVA程式

Ant 中可以使用 java命令實現運行JAVA程式的功能。可以在上面的build.xml基礎上做修改來實現:

<?xml version="1.0" ?>
<project name ="javacTest" default="run" basedir=".">
<target name="clean">
<delete dir="${basedir}/build"/>
</target>
<target name="compile" depends ="clean">
<mkdir dir ="${basedir}/build/classes"/>
<javac srcdir ="${basedir}/src" destdir ="${basedir}/build/classes"/>
</target>
<target name="run" depends ="compile">
<java classname ="HelloWorld">
<classpath>
<pathelement path="${basedir}/build/classes"/>
</classpath>
</java>
</target>
</project>

接著,就可以在主控台看見輸出:” hello world!”

3. 使用jar命令生成jar檔

還可以在上例的基礎上更進一步,來生成jar包,可在run 這個 target 下再加上如下 target :

<?xml version="1.0" ?>
<project name ="javacTest" default="jar" basedir=".">
<target name="clean">
<delete dir="${basedir}/build"/>
</target>
<target name="compile" depends ="clean">
<mkdir dir ="${basedir}/build/classes"/>
<javac srcdir ="${basedir}/src" destdir ="${basedir}/build/classes"/>
</target>
<target name="run" depends="compile">
<java classname ="HelloWorld">
<classpath>
<pathelement path="${basedir}/build/classes"/>
</classpath>
</java>
</target>
<target name="jar" depends="run">
<jar destfile="helloworld.jar" basedir="${basedir}/build/classes">
<manifest>
<attribute name="Main-class" value="HelloWorld"/>
</manifest>
</jar>
</target >
</project>

其中,project的default 屬性設置為應設為jar,ant運行完畢後,可看到在項目的根目錄下生成了一個 helloworld.jar的jar包 。可通過運行以下命令來執行該jar包:

java -jar helloworld.jar

4. 使用war命令打包JAVAEE項目

建立一個JAVAEE項目,其中src 為JAVA源代碼目錄,WebContent為各jsp存放目錄,lib 為項目引用的的包的目錄。在WebTest項目目錄下建立了build.xml 檔,該檔為該工程的 Ant 構件檔。

|WebContent
|src
|build
|classes
|WebContent
|META-INF
|MANIFEST.MF
|WEB-INF
|lib
|classes
|HelloJSP.jsp
|build.xml

讀者可以 src 目錄下放入在前續例子中開發的 HelloWorld.java 檔,並在 WebContent下建立 HelloJSP.jsp 檔,其內容很簡單,就是輸出 Hello 資訊,代碼如下所示:

<%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "HTTP://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta HTTP-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>war test for ant</title>
</head>
<body>
Hello JSP!Hello Ant!
</body>
</html>

接下來編寫 build.xml 檔,其內容如下:

<?xml version="1.0" encoding="UTF-8" ?>
<project name ="WebTest" default ="war" basedir =".">
<property name ="classes" value ="${basedir}/build/classes"/>
<property name ="build" value ="${basedir}/build"/>
<property name ="lib" value ="${basedir}/WebContent/WEB-INF/lib"/>
<!-- 刪除build 路徑-->
<target name ="clean">
<delete dir ="${build}"/>
</target>

<!-- 建立build/classes 路徑,並編譯class 檔到build/classes 路徑下-->
<target name ="compile" depends ="clean">
<mkdir dir ="${classes}"/>
<javac srcdir ="${basedir}/src" destdir ="${classes}"/>
</target>

<!-- 打war 包-->
<target name ="war" depends ="compile">
<war destfile ="${build}/WebTest.war" webxml ="${basedir}/WebContent/WEB-INF/web.xml">
<!-- 拷貝WebRoot 下除了WEB-INF 和META-INF 的兩個檔夾-->
<fileset dir ="${basedir}/WebContent" includes ="**/*.jsp"/>
<!-- 拷貝lib 目錄下的jar 包-->
<lib dir ="${lib}"/>
<!-- 拷貝build/classes 下的class 檔-->
<classes dir ="${classes}"/>
</war>
</target>
</project>

在C:\ThinkInJAVACode\WebTest目錄下運行ant後,就生成了WebTest.war 檔了,然後可以將其放入Web容器(如Tomcat)的相應目錄下(${Tomcata安裝目錄}\webapps)運行該web項目了。