[閱讀筆記] 異數

心得小結(2017.一讀)

作者認為成功的主要原因是優勢的累績與努力,
我們常說時間花在哪裡,成就就在哪裡,
但是許多傑出的例子(莫札特與比爾。蓋茲),
除了努力外,還有幸運的(?)際遇,
在很小的時候就開始邁向傑出,
這讓我想到福原愛;從小就接觸桌球又有母親(導師)親自調校,
但作者最終歸向時代與文化,兩個不可控的因素。
努力只能讓你成為比一般人好的人,
而加上際遇(時代與文化),才能成為傑出(ex:世界前 100 之類),
相比「我比別人更認真-刻意練習.讓自己發光」,
書比較強調導師與信念(熱情)的重要性,
異數比較偏向命定論。

並沒有很推薦,但是書中的馬太效應,
以及透過學習別的語言來改變文化帶來的
值得反思 .

筆記

ch1 好上加好的馬太效應

  1. 成功是優勢的累積
  2. 我們把成功與個人特質畫上等號,其他人因而失去出頭的機會。
    我們制定出來的規則反而壓抑成就,我們太早宣布某些人是失敗者。

ch2 一萬個小時的努力

  1. 他們的成功不完全是自已打造出來的,也是他們生長的時代造就的。

ch3 天才的迷思

ch4 天才的迷思.II

  • 才智和成就沒有絕對關係
  • 抽象智能與實用智能
    • 實用智能:社交常識與處事能力(後天習得,家庭),知道在什麼時候用什麼方式以及說什麼,以達到最大效果
  • 階級的文化優勢
    • 勞動階級和貧窮人家的孩子,保守不信任別人,厭惡權威,拙於交際

feedback:

  1. 千里馬常有,伯樂不常有。
  2. 我是勞動階級和貧窮人家的孩子
  3. 人脈的重要性。
  4. 如何與權威人士交際?

ch5 猶太律師的啟示

  1. 什麼都作,有生意上門就好了
  2. 機會其實是隱藏在逆境當中
  3. 你的父母是做什麼的,和你日後的成就大有關係
  4. 只要夠聰明就夠了

feedback:

  1. 七年級最慘的是哪一年次?
  2. 生逢其時 vs 生不逢時

ch 6 以血還血

ch 7 空中危機

  1. 文化的影響
  2. 以大韓航空空難為例-文化的包袱
  3. 用語言改變文化的包袱(ex:英文)

ch 8 稻米文化與數學能力

ch 9 知識力學校

後記 從牙買加到加拿大

  1. 亞洲數字發音較有規則且合乎邏輯
  2. 成功者的共通點就是努力

書單

  1. 意外的百萬富翁(Jobs 傳記)

其它

  1. 廖月娟的翻譯很不錯,感覺不出來是在讀外國人寫的書。
  2. 台灣財富排名 690 萬人是月光族 230 萬人有超 3 千萬資產

(fin)

使用 DateInterceptor 攔截SQL 語法

前言

SQL Compatibility Level 對 MsSQL 時間查詢的影響這篇文章裡,
遇到了一個令難以處理的問題, 簡單的複述一下,

  1. 我的資料表以一個 datetime 欄位當作 PK
  2. 我的 SQL DB 版本為 2016 以上 (Compatibility Level 預設值為 130)

這個時候我所有的 Entity Context 更新語法在呼叫 SaveChange 時會拋出錯誤;
原因在於 EF 所產生的 SQL Query 語法 , 會以 PK 值作為查詢條件,
而在這個時候 查詢時間的條件會精準到微秒(μs=1/1000000 秒),
但是 datetime 欄位只會記錄到豪秒(ms=1/1000 秒),
於是精準不足的部份就會補 0 ,
EX:
2017-09-24 11:55:35.37200002017-09-24 11:55:35.372
這會導致查無資料進而引發 dbupdateconcurrencyexception

解決方針

修改資料欄位

將資料庫的欄位 datatype datetime 改成 datetime2 ,
這或許是最理想的解法了, 你不需要更動程式碼,
而且會提昇你資料的時間精準度.
不過實務上,你必須考慮到你是否有 dba 的權限與即有的資料量,
當產品的核心功能依賴著這張表的時候且有巨量資料存在時,
更新欄位的資料型態的衝擊與風險或許是難以承受的.
更不用說還要考慮到整個 DB Server Cluster 的架構之類的問題.

修改 SQL Server Compatibility Level 從 130 至 120

非常不建議的作法, 除了要考慮上述權限、衝擊與風險的問題外,
如果降轉 Compatibility Level
或許會使得其他有使用到 datetime2 型態的資料欄位發生異常,
或是是失去原本精度的意義.

使用 DateInterceptor 攔截 SQL 語法

在考量上述兩種情況, 為了不增加DBA 的工作量無謂的風險與權責問題,
(其實是實務上我沒有 DB Server 的異動權限),
我們可以透過 IDbInterceptor 來欄截 Entity Framework 對 DB 存取時執行的 Query

以下是個簡單的範例,

1
2
3
4
5
6
7
8
9
10
11
12
13
public class DateInterceptor : IDbInterceptor
{
public void ReaderExecuting(DbCommand command,
DbCommandInterceptionContext<DbDataReader> interceptionContext)
{
var dateParameters = command.Parameters.OfType<DbParameter>()
.Where(p => p.DbType == DbType.DateTime2);
foreach (var parameter in dateParameters)
{
parameter.DbType = DbType.DateTime;
}
}
}

我們實作了一個 IDbInterceptor 的類別,
用來將 datetime2 的資料型別轉型成 datetime,
接下要將它掛載在 Entity Context 之中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public partial class EF6Entities : DbContext
{
public EF6Entities()
: base("name=EF6Entities")
{
DbInterception.Add(new DateInterceptor());
}

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}

public virtual DbSet<BatchUploadData> BatchUploadData { get; set; }
}

如此一來就會以 datetime 的精準度產生 SQL Query

參考

(fin)

SQL Compatibility Level 對MsSQL時間查詢的影響

大綱

這次透過 Entity Framework(EF5)作了一個簡單的資料庫更新,
恰巧的是這次更新的 Table 因為某些需求,使用 datetime 作為 Key 值.
進而引發一連串的錯誤, 最後才找到 SQL Compatibility Level 對 MsSQL 時間查詢影響.

正式環境 SQL 版本 13.0.4422.0

程式碼

1
2
3
4
5
6
7
8
9
10
11
12
13
public void UpdateBatchUploadData(BatchUploadDataEntity batchUploadDataEntity)
{
using (WebStoreDBEntitiesV2 context = this.LifetimeScope.Resolve<WebStoreDBEntitiesV2>())
{
var item = (from batchUploadData in context.BatchUploadData.Valids()
where batchUploadData.BatchUploadData_Id == batchUploadDataEntity.BatchUploadData_Id
select batchUploadData).FirstOrDefault();

this.MapBatchUploadData(batchUploadDataEntity, item);

context.SaveChanges();
}
}

如上面程式所示, item 是透過 Key 值 BatchUploadData_Id 取回來的物件.
MapBatchUploadData 是一段簡單的程式碼,
單純的將 batchUploadDataEntity 的值 mapping 到 item
再呼叫 SaveChanges , 卻引發了 dbupdateconcurrencyexception

錯誤畫面

錯誤訊息

1
2
存放區更新、插入或刪除陳述式影響到非預期數目的資料列 (0)。
這些實體載入之後可能被修改或刪除了。請重新整理 ObjectStateManager 實體。

這一段訊息的意思就是: Entity Framework 預期更新了0筆資料,與它所預期的不符, 所以拋出錯誤。

原因

透過用Sql Profiler我們錄製到了以下的 SQL

1
2
3
4
5
6
exec sp_executesql N'update [dbo].[BatchUploadData]
set [BatchUploadData_StatusDef] = @0, [BatchUploadData_UpdatedTimes] = @1, [BatchUploadData_UpdatedDateTime] = @2
where (([BatchUploadData_Id] = @3) and ([BatchUploadData_CreatedDateTime] = @4))
select [BatchUploadData_Rowversion]
from [dbo].[BatchUploadData]
where @@ROWCOUNT > 0 and [BatchUploadData_Id] = @3 and [BatchUploadData_CreatedDateTime] = @4',N'@0 varchar(30),@1 tinyint,@2 datetime2(7),@3 bigint,@4 datetime2(7)',@0='ProcessFailed',@1=1,@2='2017-09-16 11:29:35.3720061',@3=52,@4='2017-09-05 18:53:36.3530000'

請注意到 @4 datetime2(7) … @4=’2017-09-05 18:53:36.3530000’
如果將 datetime2(7) 改為 datetime或是將查詢語句改為 @4='2017-09-05 18:53:36.353 就能正確更新資料.

本機實測 (SQL 版本 12.0.4459.0)

透過本機寫了一小段的 SQL 作測試,

竟然不會有問題!!!

這跟 SQL Compatibility Level 有關,
mssql 2014 預設是 120, 2016 預設是 130,
Datetime2 在 120 跟 130 的結果會不一樣.

解決方法

主要的查詢與更新 SQL 是 Entity Framework 產生的,
所以我無法透過修改 SQL 的方式解決這個問題,
而正式環境的 SQL Compatibility Level 調整將會牽一髮動全身
且基於版本演進, 往新的版本靠攏是合理的選擇
暫時的解法是透過修改 edmx ,
不讓 datetime 作為整個 table 的 Key 值.
較好的解法是升級 Entity Framework
透過 Entity Framework 的機制, 指定查詢時間的精準度.
實作的部份未來再補上.

參考資料

(fin)

將 Express 網站整合 TypeScript 開發

原因與目的

技術問題

  • grunt/gulp/webpack
    • 選擇 gulp 希望能切換到 webpack
  • TypeScript

初期目標

  • 把所有 js 改成 ts
  • 相同指令即可完成編譯與開啟站台
  • 可部署到正式環境

構想

原本我是想把整個專案重新編譯至另外一個資料夾中,
再由該資料夾設為起始專案執行
在建立 typescript 資料夾,只編譯相關的 ts 檔;
至於哪些是相關的 ts 檔?

  1. app.ts(編譯為app.js)
    在根目錄作為所有 request 入口的起始檔案,用來解析 request 需要對應的 router
    這裡可能會有一些共用的商務/系統邏輯或是錯誤處理

  2. 所有的 router
    這是最接近使用者的 business logic code,影響返回的頁面與呈現的資料,
    通常這裡主要的目的是組合來自不同的 service 的資訊,再返回給 view 層,
    不過有時候也會處理一些顯示邏輯.

  3. 所有的 service
    這層擁最主要的商務邏輯,大多會依功能性作區分(ex:授權、會員、購物車等…)
    也有專門的 service 提供共用的方法及模組,
    並透過 repository 取得/更新資料

  4. 所有的 repository
    這層最主要的功能是直接與資料庫作存取

  5. 其它
    例如:Interface, Class, Enum 或是一些框架所需要額外的方法.

實作

定義好需要修改的範圍後,我建立了一個 typescript 資料夾
裡面會建立相對應的router,servicerepository資料夾
與一個app.ts檔案.

一開始將所有 js 檔案依照相對的位置,照抄複製放入對應位置,並將副檔名改為.ts
接下我將利用 gulp 幫執行相關的編譯行為.
我們可以預期編譯產生的.js檔可以執行,因為 Typescript 是 Javascript 的 Super Set

gulp

  • 安裝 gulp
    npm install gulp -g
    npm install gulp --save
    npm install gulp-typescript --save

  • 安裝相關模組
    npm install @types/node --save-dev
    npm install --save @types/express
    npm install --save @types/morgan
    npm install --save @types/cookie-parser
    npm install --save @types/httperr
    實際安裝哪些模組與專案所需要的有關,通常只要在 npm 搜尋npm @types module_name
    就可以找得到,不過有時候也會有找不到情況
    這個時候很苦惱了,我們想要 ts 的強型別即時除錯,但是自已刻又太自虐,
    我的想法是只有自已寫的router,servicerepository有需要即時除錯.
    之後實務上有遇到再回來補充.

  • 設定 gulp file

1
2
3
4
5
6
7
8
9
10
11
12
13
var gulp = require('gulp');
var tsc = require('gulp-typescript');

gulp.task('app', function() {
return gulp.src(['typescript/app.ts','typescript/**/*.ts'])
.pipe(tsc({
target: "es2017", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */
module: "es2015", /* Specify module code generation: 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
lib: ["es2015","dom"],
noImplicitAny: false,
}))
.pipe(gulp.dest('./'));
});

我們不需要tsconfig.json這個檔案,直接可以寫成 json 物件
另外記得設定 src(ts 所在的位置)與 dest(輸出 ts 的位置)

  • 設定 package.json
1
2
3
4
5
6
7
8
"scripts": {
"start": "node ./bin/www",
"dev": "supervisor ./bin/www",
"test": "node_modules/.bin/nightwatch",
"e2e": "npm-run-all --parallel start test",
"run": "node ./bin/www",
"ts": "gulp app"
}
  • 執行 npm run ts
  • 檢查輸出的 js,再說一次因為 Typescript 是 Javascript 的 Super Set
    所以可以預期會產生相同的檔案內容(副檔名變成.js)
  • 運行網站確定功能正常

修改 ts 檔

雖然 js 檔已正常產生,但是其實這一切都是假的!
原因是我們的 ts 檔其實仍然在寫 js
修改ts檔
在執行 gulp 的過程當中,應該可以看到一些提示訊息.
由於有各種情況,就不一一說明了.
我在這此改版的疑難就附在下面,如果有遇到相同的問題,可以作為參考.
如果有不同問題,也可以留言討論喔(雖然我覺得是自行 google 會快一些) XD

疑難

參考

  1. npm scripts 使用指南
  2. typescript getting error TS2304: cannot find name ‘ require’
  3. Express and Typescript - Error.stack and Error.status properties do not exist
  4. gulp-typescript
  5. Promise static method give error

(fin)

[記錄]串接 GOOGLE API 取得資料(二)

前情提要

前篇說明為什麼我要作「精神力評鑑」

原因是為了記錄自已的精力,能更有效率使用時間,進而提昇個人的生產力.

應用

有了數據後, 就要看怎麼運用.
Google 的表單,可以自動產生回應結果試算表,
持之以恒的每天記錄,很快就有上百千筆的資料.

我打算取得這些資料後,繪制成分佈圖
這樣就可以知道,我的黃金時間是在每一天的什麼時段.

如何透過 GOOGLE Sheets API 可以取得資料.

Google Cloud Platform,建立起 Google API 的服務。

  1. 前往Google API Wizard,建立或選擇專案。
  2. 建立憑証,選擇 OAuth Client ID
    • 在這裡我會一次性的建立起所有環境(開發、測試、正式)的憑証 。
  3. 下載 JSON 放置專案的指定位置.
  4. 如何取得授權與取得資料,請參考QuickStart
    • 安全考量,我不會將 client_secret.json 與取回的 token 加入版本控制
    • 以 Google 試算表為例,如何取得spreadsheetId? 很簡單,網址上就可以取得。
      1
      2
      3
      ex:
      https://docs.google.com/spreadsheets/d/1qpyC0XzvTcKT6EISywvqESX3A0MwQoFDE8p-Bll4hps/edit#gid=0
      的spreadsheetId就是1qpyC0XzvTcKT6EISywvqESX3A0MwQoFDE8p-Bll4hps

說明

以 QuickStart 的程式為例 ,
下載回來的檔案 client_secret.json
可以提供 clientSecretclientId 與授權後轉導的 url ,
當程式執行時, 便會透 googleAuth 去取得授權 ,
過程之中會需要使用者作驗証, 驗証完成即取得授權 ,
授權有一定的效期, 故一段時間之後需要重新取得授權

其它

  1. client_secret.json 是機敏資料, 不可以放入版本控制, 需要特殊的流程步驟上傳到你的 Web Server 的位置 - Openshift 可以透過 SSH 或是 SFTP 登入來上傳client_secret.json - CI 以 JENKINS 為例 ; 可以使用 Publish over SSH 上傳檔案
    - 需要注意 CI Server 要有 Web Server 的 SSH Key - LINUX 複製資料夾語法 cp -rf src/folder/. target/folder

  2. 在正式公開的環境上可能會發生Error: invalid_scope的錯誤 ,可以參考

  3. 我最後是使用d3.js作為繪圖的 library , 因為網路上的資源相當的多 , 而且這次的著墨並不多 , 所以不在這裡介紹.

Demo

參考

  1. Openshift SFPT
  2. Linux CP

(fin)

[記錄]串接 GOOGLE API 取得資料(一)

原由

最近看了一本書,「最有生產力的一年」. 書如其名,是一本有關生產力的書,作者用了一年的時間,

以自已作實驗,測試各種方法並檢驗是否有效.

作者認為,時間 x 專注力 x 精力 = 生產力, 在他眾多的方法之中,

「黃金時間」與「精神力評鑑」是一組相關的方法.

簡單的說,一個人的精力狀況,會隨著一天的生理、心理等客觀或主觀因素起起伏伏,

就像潮汐一樣,而作者認為人應該在精力最好的時段,作最有價值的事.

這個時段就被稱作「黃金時間」.

有些人黃金時間在早上,有些人在晚上,每個人都不相同,

因此需要記錄精神狀態來判斷.

規劃

於是我設計一組簡單的方法來記錄精神狀態

透過 google 表單用來作記錄,

狀態我以 10 分制來作為評選, 再兩兩一組, 共分為五組「優、良、佳、可、劣」

在評鑑時先以五分法判斷精力狀況, 再以感覺是好一點或差一點來選取分數.

行動化

以我個人的習慣, 手機其實是不離身的, 如果每次記錄都要在個人電腦上作業, 其實是很不方便的.

所以我參考了電腦玩物的「Excel 記帳雲端進化! Google 表單比記帳 App 還好用

將我的表單(偽)APP 化

如此一來便完成了方便記錄的工具, 至於記錄下來的數據如何應用

又是後話了. 不過順帶一提,我會透過整點報時的工具來提醒自已作記錄,

另外我不像「最有生產力的一年」的作者特別準時睡覺,或是不喝咖啡,

因為不準時睡覺跟嗜飲咖啡是我目前的習慣,我認為抱持平常的狀態去記錄才有意義.

又或者當我戒掉咖啡或是開始早睡早起的習慣時,我就可以用記錄來評量這樣的行為是否有所改善.

(fin)

[活動筆記] 測試即學習

應該知道的事

流程

讀這篇文章的人,或許可以找一位朋友來作相同的流程練習

  1. 一張不明所以的圖片當作受測對象,任意發散的詢問測試問題
  2. 分類所有的問題為 WHY、HOW、WHAT
  3. 畫出 Golden Circle,核心是 WHY
  4. 透過 WHY 揭露受測對象圖片的本質(TAROT),收斂測試範圍
  5. 重新詢問測試問題,儘可能的發散
  6. 所有人交流自已看到可能的測試問題
  7. 重複 5~6 步,有意識的去覺察自已的感覺
    • 別人注意到的問題你為什麼沒發現?
    • 別人如何發現問題?
    • 你有沒有辦法發現相同的問題(殊途同歸)?

測試的概念

  1. 受測者
  2. 問題
  3. 答案
  4. 預期的結果(test oracles)

問題的核心 Know Your Mission(KNW)

  1. WHY ?
    本次分享的重點,如何了解、探索、學習產品的知識,給客戶的價值等… 。
  2. HOW ? => 如何作到?
  3. WHAT ? => 產生測試項目
  4. TEST ? => 執行測試,回饋並且回到第一步 Loop。

應該觀注的資訊 information is learning

  1. Customer
  2. Product
  3. Project
  4. Mission

透過 WHY 的詰問,了解你的任務(Know Your Mission)
學習到受測對象的本質,排除不必要的測試發想。
收斂了測試範圍,決定了測試方向。

探索測試

  1. 儘可能的發散
  2. 摹仿別人看事情的角度
  3. 有意識的學習(mindful learning)
    • 記錄
    • 總結
    • 歸納問題的核心
    • 給它起一個名字(沒有專有名詞的話)
    • 有意識的逃離第一印象
    • 小心不經意的盲區(Inattentional blindness)
    • 數量 X 練習 X 思考 X 學習 = 提昇

回饋

  1. 刻意練習 vs 有意識的練習,類似的東西,但是沒有具體的方法
  2. 課程中提到的影片

(fin)

CI/CD 環境建置筆記(二) - 在 windows 安裝 Jenkins 之旁門左道私有雲

應該要知道的事

問題

在 windows 上安裝了 Jenkins , 也建立了一個新的作業 ,
讓程式可以透過 Jenkins 執行自動化部署。
不過實務上仍有缺點,那就是只有這台機器可以執行部署 ,
最理想的狀況是擁有一個在雲端的 CI Server ,
隨時隨地想部署只需要能連上網 , 登入執行就好
這部份可以試試看 Travis-CICircleCI之類的服務 ;
不過對於只熟悉 Jenkins 的我來說 ,
我想了另一套解決辦法(旁門左道) ,
不過至少解決了我目前的需求 ,
在公司與家中隨時都能透過本機 Jenkins Server 進行部署 ,
並且不用花時間同步設定值.
在花時間研究Travis-CICircleCI的部署方式之前 ,
算是一個折衷的方式 .

方案

  1. 首先你要有 Dropbox
  2. 下載 Windows 版的 Jenkins
  3. 安裝在 Dropbox 資料夾內
  4. 在另外一台電腦 , 進行相同的安裝

想法很簡單 , 透過 Dropbox 與 Jenkins Service
在兩台電腦安裝 Jenkins
只要將 Jenkins Service 的啟動路徑
設定在 Dropbox 中 ,
就可以達成我們的目標 .

問題

  1. Windows 要如何修改 Jenkins Service 的啟動路徑 ?

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Jenkins

修改路徑以符合你本機的檔案位置即可。

(fin)

[活動筆記] 單元測試這樣玩就對了

應該知道的事

案例一、數值區間

1
2
3
4
5
6
假定給任一整數區間
ex:
(1,6] = {2,3,4,5,6}
[-2,4) = {-2,-1,0,1,2,3}
透過一個function(x)檢查x是否包含在整數區間內,
並撰寫測試,驗証 function(x)是對的。

解析

如上範例所示,
「(」「)」小括號(parentheses)表示OPEN(不包含,大於或小於)
「[」「]」中括號(square brackets)表示CLOSE(包含,大於等於或小於等於)
(1,6] , 代表這個區間大於 1 小於等於 6,包含的整數有 2、3、4、5、6
[-2,4), 代表這個區間大於等於-2 小於 4,包含的整數有-2、-1、0、1、2、3

解析

這題比較單純,只需要考慮所有的情況,
並且寫成單元測試即可。

  1. x 落在區間內
  2. x 落在左邊界外
  3. x 落在右邊界外
  4. x 落在左邊界上,左邊界為OPEN
  5. x 落在左邊界上,左邊界為CLOSE
  6. x 落在右邊界上,右邊界為OPEN
  7. x 落在右邊界上,右邊界為CLOSE

有幾種特殊的情境,特別說明一下

  1. 假設區間為(0,1),這個區間是不包含任何整數
  2. 假設區間為(1,1),這個區間是不包含任何整數,且不包含任何數值
  3. 假設區間為[1,1],這個區間恰巧包含 1 個整數,且只包含 1 這個整數
  4. 假設”區間”為[2,1],或任何左邊界大右邊界的表示,這不是一個正確的區間,將要作例外處理。

讓我們回歸單元測試,
這裡的重點是一個測試只作一件事,
只把一個情境釐清,並且在測試的程式碼中
明確的表達測試目的

1
2
3
4
5
6
7
8
9
10
11
12
private int leftBound = 1;
private int rightBound = 6;
private int testNum = 4;

[TestMethod]
public void IncludeWhenLeftOpenRightClose()
{
var checker = new RangeChecker(Bound.Open,this.leftBound,Bound.Close,this.rightBound);
bool expect = false;
bool result = checker.IsContains(testNum);
Assert.IsTrue(result);
}

案例二、現在時間轉字串

1
2
3
寫一個方法GetNowString,不傳入任何參數,
取得現在的時間字串,需要精準到豪秒。
再寫一個測試去測試這個方法是對的‧

版本 1

最簡單的寫法:

1
2
3
4
5
6
7
public class DateHelper
{
public string GetNowString()
{
return DateTime.Now.ToString("yyyy-MM-dd hh:mm:ss ff");
}
}

撰寫測試

1
2
3
4
5
6
7
8
9
[TestMethod]
public void GetNowString()
{
var
//// 寫不下去,因為我們無法凍結系統的時間
string expect = "2017-04-19 20:45:17.88";
string result = dater.GetNowString();
Assert.AreEqual(expect, result);
}

解析 1

GetNowString與系統的時間DateTime.Now,
是具有耦合性,要解耦需要透過一些 IoC 的手段去處理。

版本 2

利用繼承的方法,作出假的類別

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public class DateHelper
{
protected DateTime now;
protected virtual DateTime GetNow()
{
now = DateTime.Now;
return now;
}

public string GetNowString()
{
GetNow();
return now.ToString("yyyy-MM-dd HH:mm:ss.ff");
}
}

class StubDateHelper: DateHelper
{
protected override DateTime GetNow()
{
return now;
}

public void SetNow(DateTime datetime)
{
now = datetime;
}
}

撰寫測試

1
2
3
4
5
6
7
8
9
10
[TestMethod]
public void GetNowString()
{
StubDateHelper dateHelper = new StubDateHelper();
var fakeNow = new DateTime(2017,4,19,20,45,17,880);
dateHelper.SetNow(fakeNow);
string expect = "2017-04-19 20:45:17.88";
string result = dateHelper.GetNowString();
Assert.AreEqual(expect, result);
}

解析 2

基本上這樣就可以測試了,
原來的代碼,經過一定的重構,
透過virtual方法 GetNow,
Datetime.Now作了隔離
適當利用假類別,取代掉 GetNow 的方法。

這樣夠好了,但是我們可以看看另一種作法

版本 3

先看看我們的DateHelper,
在這裡我們將 GetNow 交由 IDateProvider 的類別去實作,
如此一來就斷開了耦合性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class DateHelper
{
private IDateProvider DateProvider;

public DateHelper(IDateProvider dateProvider)
{
this.DateProvider = dateProvider;
}

public string GetNowString()
{
var now = this.DateProvider.GetNow();
return now.ToString("yyyy-MM-dd HH:mm:ss.ff");
}
}

實作 IDateProvider 的類別,
在這裡其實不重要.

1
2
3
4
5
6
7
public class DateProviderV1 : IDateProvider
{
public DateTime GetNow()
{
return DateTime.Now;
}
}

讓我們看看測試,
在這裡我們透過一個假的IDateProvider的實作DateProviderStub,
完成了測試,
IDateProvider 將DateTime.Now作了隔離,
並且提供更容易修改的假物件(僅僅需要實作觀注的方法即可,不用擔心繼承帶來的附作用)

1
2
3
4
5
6
7
8
9
10
[TestMethod]
public void GetNowString()
{
DateProviderStub dateProvider = new DateProviderStub();
dateProvider.now = new DateTime(2017, 4, 19, 20, 45, 17, 880);
var dateHelper = new DateHelper(dateProvider);
string expect = "2017-04-19 20:45:17.88";
string result = dateHelper.GetNowString();
Assert.AreEqual(expect, result);
}
1
2
3
4
5
6
7
8
public class DateProviderStub : IDateProvider
{
public DateTime now;
public DateTime GetNow()
{
return now;
}
}

圖例解析

我們剛剛究竟幹了什麼?
我們剛剛究竟幹了什麼?
看看原本的情況,本來的方法因為相依與Datetime而無法測試
相依Datetime
讓我們開始下刀,
先用一個新的方法GetNow
將它與待測的方法作分割,
但是對整個類來說仍舊是耦合。
耦合
繼續把這刀往下切,
我們墊一層介面,
待測方法不再直接呼叫GetNow
而是透過介面執行,當然會有額外實作介面與注入的功(請參考程式碼,不在此處繪出了.)
透過介面執行
最後,別忘了我們的目的
測試原本的待測方法,
我們可以透過一個假的類,
來操控他的行為(ex:凍結時間).
如此一來,就可進行測試了.
測試
另外,這種被待測方法呼叫後
會回傳一個假值的方法或類
被叫作STUB
STUB

案例三、發送郵件

事先聲明,這題沒有程式碼,
有興趣實作的人可以試試看.
如果可以分享實作後的資訊給我更好 XD

Q:註冊發送郵件如何寫單元測試?

解析
很明顯的發送郵件需要依賴外部的郵件系統,
這裡就會有耦合性,我們可以參考案例 2 的方式解耦
不過發送郵件並不會有回傳值,
我們要如何驗証正確性呢?

A:檢查調用次數、參數

圖例解析
在案例 2 的單元測試,
我們透過 STUB 偽造的回傳值完成測試
並執行驗証.
但是在沒有回傳的值的方法中(被稱作MOCK)
我們只能透過傳遞的參數(如果有多載)
與方法被調用的次數來進行驗証。

MOCK

重點摘要

  • 單元測試要能清楚表達測試的目的(達意)
    • 命名
    • 減少意外的細節
  • 單元測試一次只作一件事
  • new 本身就是一種邏輯 一種偶合
  • static 是一種高偶合
  • 繼承也是高偶合,能使用繼承的情境很少
    • A is a B 通常只有這種情境才適合繼承
  • STUB & MOCK
    • STUB 會有回傳值,可以在 Unit Test 作驗証(ASSERT)
    • MOCK 沒有回傳值,可以在 MOCK 本身中 作驗証(ASSERT)

其它

  • SLIM
  • 注入相依的幾種方式
    • Pool
    • Constructor
    • Property
  • 書單 : XUnit Test Patterns

直播影片

如果連結失效,煩請告知.

文章內容如有謬誤,煩請指正.

(fin)

[閱讀筆記] 其實,你不是沒有時間

六個工具與餐盤

  • 原則:工具簡單至上
  • 面對突發狀況
  1. 月計劃表 (google calendar)
    • 與自已約會(保留給自已的時間)^註 1^
    • 工作與私人不要分開
    • 日期與期限
  2. Todo List (inbox)
    • 提醒與集中管理
    • 每天都要檢查一遍^註 2^
  3. 日計劃表 (google calendar)
    • 當日約會 & 活動清單
    • 活動清單是你當天的籌碼 也是出現突發
  4. 隨手記 (筆記本&Evernote)
    • 接受任務,再轉到其他工具之中^註 3^
  5. 隨身資料庫 (google drive)
    • 將整理過的資料封存在隨手可得的地方
      ex: 上課或分享的簡報、 專案記錄等…有大量圖片、文件等
  6. 溝通工具 (line & slack)
    • 一天檢查電子郵件 3~6 次就夠了

把時間變空間(視覺化)

  • 個人、家庭、工作取得平衡
  • 安排 30 分鐘規劃一天的活動
    • 創造性活動安排在精力最佳的時段
    • 安排獨處的時間
    • 安排恢復精力的時間
    • 預留時間給可能發生的突發事件
  • 將所有的事情分為紅綠黃(灰)^註 4^,綠色應該越多越好,灰色越少越好。
    6 am7 am8 am9 am10 am11 am12 am1 pm
    5 am本日活動隨手記2 pm
    4 am3 pm
    3 am4 pm
    2 am5 pm
    1 am12 am11 pm10 pm9 pm8 pm7 pm6 pm

應變執行,計劃趕不上變化

  • 調整事情的順序
  • 養成習慣
  • 臨機應變
  • 小心多工
  • 保留風格

  1. 分別為聖的時間,請參考與時間作朋友
  2. 利用分別為聖的時間檢查
  3. 餐盤中間的區塊可以用來作當日的活動清單 & 隨手記
  4. 可以參考四象限 重要-緊急(紅)、不重要-緊急(黃)、重要-不緊急(綠)、不重要-不緊急(灰)

(fin)

Please enable JavaScript to view the LikeCoin. :P