[實作筆記] 單元測試與重構記錄(一)

前情提要

有幸參與了一個跨國的專案,
為了快速上線,決定將整套原本在台灣程式碼搬移到跨國專案上,
上線後再依使用者的需求調整開發功能,
而在搬移的過程中,有需多模組並未開啟。

現況

遺留代碼 → 跨國遇到的問題,
主管要求用複制整個台灣的環境、流程與代碼到國外的環境之上,
但是要求 One Code Base:

  1. 台灣的情境不一定適合國外環境
  2. 修改功能與台灣邏輯抵觸時,台灣 PM 不一定允許修改
  3. 到處寫例外 if、switch 處理 台灣其它國 的問題
  4. 完整重建整套系統非常費功,沒有資源時會犧牲子系統,導致國外環境部份功能無法使用或是必須與台灣共用

用明朝的劍,斬清朝的官

實務需求

將本來跨國未開啟的折扣活動模組打開,
簡單的流程大致如下:
購物車 → 取得購物車資料 → 折扣活動 → 計算

實務上,整個流程作了許多事
整個流程作了許多事

應該說作了太多事.
作了太多事

程式碼有壞味道,卻不能修改(重構).
因為沒有單元測試保護.

單一的 Process,複雜度過高的方法(12)

CalculateShoppingCartPromotionDiscountV2Processor.Process()

複雜度過高的方法

目標與執行順序

  1. 由 PM 或 QA 補足整合測試情境到足夠
    • 由實務上的需求來認定
  2. 刪除台灣的測試
  3. 解析 CalculateShoppingCartPromotionDiscountV2Processor
  4. 補上單元測試
    • Code Coverage(測試覆蓋率)
  5. 重構

最終的目標是重構

  • 心態:沒有時間,完美的借口
  • 重構前要先作整合測試
  • 現有的整合測試的缺陷
    1. 測試項目不符合馬來西亞現狀
    2. 測試項目未處理多語系
    3. 測試項目未處理小數點
    4. 測試項目難以閱讀
    5. 測試項目有重覆的覆蓋範圍
  • RD 與 PM 與 QA 合作

UAT 讓「人」讀得懂

原本的 UAT (RD)

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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
場景: case37:商品活動+全店活動(有排除);宅配,運費50元,滿350免運
.全店活動 / 有排除商品;滿額打折,多階(滿第1階),跨溫層
.折扣條件:滿199元,打95折 / 滿299元,打89折 / 滿399元,打84折
.商品活動;滿件折現,單階,跨溫層
.折扣條件:滿2件,折45元
假設 購物車中溫層"Freezer"商品為
| SalePageId | SaleProductSKUId | Price | Qty |
| 50 | 50 | 75 | 1 |
| 27 | 27 | 66 | 2 |
並且 購物車中溫層"Refrigerator"商品為
| SalePageId | SaleProductSKUId | Price | Qty |
| 26 | 26 | 55 | 2 |
並且 購物車中溫層"Normal"商品為
| SalePageId | SaleProductSKUId | Price | Qty |
| 25 | 25 | 2 | 2 |
並且 活動"1"範圍設定為
| TargetType | TargetIdList |
| Shop | 1 |
並且 活動目標排除商品頁為
| PromotionId | TargetExcludeSalePageList |
| 1 | 50 |
並且 現折活動"1"的折扣為
| Id | TypeDef | TotalPrice | DiscountTypeDef | DiscountRate |
| 1 | TotalPriceV2 | 199 | DiscountRate | 0.95 |
| 2 | TotalPriceV2 | 299 | DiscountRate | 0.89 |
| 3 | TotalPriceV2 | 399 | DiscountRate | 0.84 |
並且 活動"2"範圍設定為
| TargetType | TargetIdList |
| PromotionSalePage | 0 |
並且 活動目標商品頁為
| PromotionId | TargetSalePageList |
| 2 | 50,25,26,27 |
並且 現折活動"2"的折扣為
| Id | TypeDef | TotalQty | DiscountTypeDef | DiscountPrice |
| 4 | TotalQtyV2 | 2 | DiscountPrice | 45 |
當 計算活動折扣
那麼 購物車商品折扣後為
| SalePageId | SaleProductSKUId | PromotionDiscount |
| 50 | 50 | -12 |
| 27 | 27 | -25 |
| 26 | 26 | -19 |
| 25 | 25 | 0 |

「人」寫的 UAT

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
29
30
31
32
33
34
35
36
37
場景: 商品有兩檔活動,全店活動與商品活動;
.第一檔是全店活動 / 排除商品B;滿額打折,
.折扣條件:滿10元,打95折 / 滿20元,打89折 / 滿30元,打84折
.第二檔是指定商品;滿件折現,單階,指定商品A 、商品B
.折扣條件:滿2件,折3元

當 購物車中的商品為"商品A 與商品B"
| Title | SalePageId | SaleProductSKUId | Price | Qty |
| 商品A | 25 | 25 | 7.45 | 2 |
| 商品B | 26 | 26 | 4.45 | 2 |
並且 第"1"檔是全店活動 ,排除以下商品
| Title | SalePageId |
| 商品A | 26 |

而且 第"1"檔折扣條件是"滿額打折,滿10元,打95折 / 滿20元,打89折 / 滿30元,打84折",如下
| Id | TypeDef | TotalPrice | DiscountTypeDef | DiscountRate |
| 1 | TotalPriceV2 | 10 | DiscountRate | 0.95 |
| 2 | TotalPriceV2 | 20 | DiscountRate | 0.89 |
| 3 | TotalPriceV2 | 30 | DiscountRate | 0.84 |

並且 第"2"檔是指定商品,指定商品如下
| Title | SalePageId |
| 商品A | 25 |
| 商品B | 26 |
| 商品C | 27 |
| 商品D | 50 |

而且 第"2"檔折扣條件是"滿件折現,滿2件,折3元",如下
| Id | TypeDef | TotalQty | DiscountTypeDef | DiscountPrice |
| 4 | TotalQtyV2 | 2 | DiscountPrice | 3 |

當 計算活動折扣

那麼 購物車商品折扣金額及折扣後小計為
| Title | SalePageId | Price | Qty | PromotionDiscount | TotalPayment |
| 商品A | 25 | 7.45 | 2 | -2.55 | 12.35 |
| 商品B | 26 | 4.45 | 2 | -1.11 | 7.79 |

與 PM 及 QA 討論後 , 寫的 UAT 可閱讀性提高了
這裡用到了一些小技巧 , 讓 Cucumber 文件的可讀性更高
而不會有太多的重複方法 , 比如說把描述性的文字當作參數傳遞
實際上測試不會使用到這些變數 ,但是可以增加可讀性 .

Skip 台灣測試

因為已經有了跨國所需要的測試 ,
台灣的測試便可以退場了.
實際上也不符合現況, 如多語系、時差與小數點等問題

解析 CalculateShoppingCartPromotionDiscountV2Processor

解析

  1. 無折扣的情境
  2. 新舊相容的情境
  3. 排序
  4. 計算折扣金額
  5. 看見相依
    1. 程式碼中有 new 別的 class 的部份
    2. 程式碼中有使用靜態方法的部份

補上單元測試

最簡單的重構,就是將整個方法內的四個邏輯
拆成四塊個子方法,並為他們加上單元測試.
修改的過程,如果有紅燈就要修改成綠燈,
而整個成品要保證整合測試與單元測試都是綠燈.

此外,重構的過程中如果過到靜態方法,
或是 new 新物件, 都很有可能是種相依,
可以透過一些方法作解耦,
參考之前的文章單元測試這樣玩就對了

重構

最後一步就是大膽的重構了,
有了測試作保護,
可以作更大範圍的重構,
如下圖示,這裡揭露了在台灣原有的繼承結構,
而紅色的部份是在跨國用不到的類別.
類別

下一步,待續…

(fin)

20171023本周要聞/心得/學習擷錄

  1. Docker 宣布拥抱 Kubernetes

    1
    2
    3
    4
    Docker Swarm与整个Docker平台紧密集成,然而并非所有人都愿意选择Swarm。
    Hykes表示,默认的Swarm已经限制了Docker用户的完整体验,
    为此,Docker公司计划提供一个无缝平台,同时支持包含Swarm和Kubernetes集群的异构部署。
    Docker企业版(EE)将很快为Kuberenetes和Swarm提供全套Docker管理服务
  2. 免費字哪裡找?使用google font

  3. 使用 TypeScript 開發 nodejs 發生以下錯誤(使用 gulp 編譯成 js)

    1
    error TS2693: 'Promise' only refers to a type, but is being used as a value here.

    安裝 @types/es6-promise 以解決問題

    1
    npm i --save-dev  @types/es6-promise
  4. 使用 netstat -ano 指令在 windows 上查詢佔用的 port 與 PID

  5. 執行 npm ls moduleName 可以列出目前專案所以相依該模組的模組

  6. 執行 npm update 更新目前專案的模組

  7. package.json裡面的的節點dependencies放的是與專案相關的模組,
    devDependencies放的是與開發相關的模組。ex:gulp,
    請考慮你使用的模組,是為了開發?還是產品真的會用到。

  8. 想法

    • 懶人包應是協助跨越門檻與看見全貌的工具,
      雖然難以避免代入個人的價值觀,但是刻意為之是不好的。
    • 自我批判是一種建立反饋的最好方式,比起以公司\社群\世界最好的人要求自已,
      以最好的自已去要求自已理應該最恰當的,錄影、看鏡子之類的方式… 。
  9. [叫人意想不到的激勵科學 (TED 中英文字幕)] (https://www.youtube.com/watch?v=rFVhkIrVDzM)

    1. 自主性
    2. 掌握度
    3. 使命感

(fin)

[閱讀筆記] 異數

心得小結(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)

Please enable JavaScript to view the LikeCoin. :P