葉顆顆
Sudo Ninja
Published in
20 min readMay 31, 2016

--

ocowchun

1. 使用 Terraform 設定 request parameters

Terraform 0.6.16 支援了設定 API Gateway Request and Response Parameter 用法如下

resource "aws_api_gateway_resource" "myResource" {
rest_api_id = "${aws_api_gateway_rest_api.myApi.id}"
parent_id = "${aws_api_gateway_resource.users.id}"
path_part = "my_method"
}
resource "aws_api_gateway_method" "myResource" {
rest_api_id = "${aws_api_gateway_rest_api.myApi.id}"
resource_id = "${aws_api_gateway_resource.myResource.id}"
http_method = "GET"
authorization = "NONE"
request_models = {
"application/json" = "Error"
}
request_parameters_in_json = <<PARAMS
{
"method.request.querystring.param1": false,
"method.request.querystring.param2": false
}
PARAMS
}
resource "aws_api_gateway_method_response" "myResource" {
rest_api_id = "${aws_api_gateway_rest_api.myApi.id}"
resource_id = "${aws_api_gateway_resource.myResource.id}"
http_method = "${aws_api_gateway_method.myResource.http_method}"
status_code = "200"
response_models = {
"application/json" = "Empty"
}
}
resource "aws_api_gateway_integration_response" "myResource" {
rest_api_id = "${aws_api_gateway_rest_api.myApi.id}"
resource_id = "${aws_api_gateway_resource.myResource.id}"
http_method = "${aws_api_gateway_method.myResource.http_method}"
status_code = "${aws_api_gateway_method_response.myResource.status_code}"
}
resource "aws_api_gateway_integration" "myResource" {
rest_api_id = "${aws_api_gateway_rest_api.myApi.id}"
resource_id = "${aws_api_gateway_resource.myResource.id}"
http_method = "${aws_api_gateway_method.myResource.http_method}"
type = "HTTP"
integration_http_method = "GET"
uri ="${var.private_api_host_name}/api/v1/my_resource"
request_parameters_in_json = <<PARAMS
{
"integration.request.querystring.param1": "method.request.querystring.param1",
"integration.request.querystring.param2": "method.request.querystring.param2"
}
PARAMS
}

2. RSpec Design Patterns

這次去 RailsPacific 聽到的分享,講者 Adam Cuppy 分享了如何有效改善你的測試程式碼,一般來說為了要完整測試你的程式,測試的程式碼都會比受測試的程式碼行數來得多,不過我們卻很常忽略了測試程式碼的可讀性與可維護性,導致每次看到落落長的測試程式碼的時候都需要花許多的時間去回想與確認當時所想要測試的內容,講者提出了幾個 patterns 來改善測試程式碼的品質,例如他提到了其中一個 pattern 叫做 Minimum Valid Object 方法如下:

  1. start with a valid object
  2. make one specific change
  3. assert the valid object is now invalid

當測試你的物件是否可以通過驗證時,如果一開始的測試物件就是不能通過驗證的,*你沒有辦法確定他是否符合你要測試的那個驗證函數*,比較好的方式為先建立一個可以通過驗證的物件,然後變動要測試的屬性,再去做測試。

其他還有需多實用的 pattern,聽完之後感覺有很多東西可以學習,非常建議看他的 slide

3. 如何開feature ticket

一樣是在 RailsPacific 聽到 Miles Woodroffe 如何管理公司的開發,當他們想到一個新的功能,希望開發者去實作的時候,要在 trello 開一張新的卡片,描述這個新功能的相關內容,他們有一個簡單的樣板我覺得非常棒,內容如下:

owner: <開票的人>

why: <要開發什麼功能,為什麼要開發>

we belive that <this capability>
will result in <this outcome>
we ill know we have succeded when <clearly defined measurable result>

透過這個方式,一方面讓開票的人可以在寫卡片的時候,再一次去想這個功能的必要性與可以帶來的效益是什麼,一方面也讓開發者可以容易了解功能的內容與實做這個功能的好處是什麼。

Kalan

這個禮拜在思考關於重構的東西,在前端還可以怎麼實踐。css 的部分已經有大致的雛形出來了,把相關的註解跟文件寫完之後再和大家分享心得。

Get your ../../ out

reference

css 部分重構完之後,再來就是要對 js 下手了。目前比較困擾的幾個點有:

  1. component 初期的部分比較亂
  2. 有些 flux、有些 redux

再來是我認為最麻煩的,就是路徑管理的問題了,因為檔案裡面有太多類似的 ../.. 出現,導致之後要對檔案做搬遷的時候還要全部重改一次,感覺非常的費工費時,也是讓人不敢輕易重構一大原因。

那時候第一個想法是,「不然我把全部的檔案存成一張路由表好了。」,不是 import 檔案本身,而是每次要 import 時,都到這個路由表去拿路徑,不但方便,而且每次對檔案做搬遷時,我只要維護好這張路由表就了。不過這個想法還沒有實作過,所以可能不是好方法也說不定…。

再來發現的第二個工具是modulesDirectories,主要是幫你把 root 定義好,每次呼叫 component 時都是以 root 當作起點而非檔案本身。

以 sudo 的檔案為例

require('../ui/comment_reply.js');
import h from '../utils/sudo_helper/dom.js';
const actionList = {
reply: 'reply',
edit: 'edit'
};

import jobsIndex from '../reducers/pages/JobsIndex.js';
import App from '../containers/jobs_index.js';
import configureStore from '../stores/JobSearchStore.js';

var Checkbox = require('../components/checkbox');
var Slider = require('../components/slider.js');
var TextInput = require('../components/text_input.js');

加入 modulesDirectories 之後,我們可以這樣寫:

require('ui/comment_reply.js');
import h from 'utils/sudo_helper/dom.js'

import jobIndex from 'reducers/pages/JobIndex';
import App from 'containers/jobs_index';
import configureStore from 'stores/JobSearchStore';

這樣 component 搬遷的時候,不會因為相對路徑改變的關係,而還要全部重寫一次,重構的意願也相對提高了。

idiomatic-css

最近一直在研究如何讓 css 的易讀性更高,並且更好做維護,目前如果搭配 webpack 的話,常見的方法有:

  1. css-module
  2. postCss
  3. radium

這些工具搭配 react 一起使用,其實可以很有效地解決 namespace 的問題。

不過主站目前想要先解決的是變數的統一命名,及 css 的規範。這部分規範好了之後,css 的管理相對也會變得簡單許多。

code smell in css

這篇文章介紹了很多關於 css 的 smell code。幾個比較重要的大概念:

  1. 避免 magic number
  2. 盡量讓 class 是 immutable
  3. 利用權重的技巧,而不是過度使用 important
  4. selector 盡量簡單

順便來複習一下 selector 的語法吧!除了用 class 來當做 selector 之外還有很多其他實用的 selector 可以使用! 像是 input 就是一個非常好使用 attribute selector 的例子:

.sudo-input {
input[type="text"] {
// text style
}

input[type="submit"] {
// submit style
}

input[type="number"] {
// ...
}
}

這樣不僅語意清楚,也可以省下不必要的 class。

Henry

1. React Native 使用 fastlane 螢幕截圖

問題

App 上架一定要上傳螢幕截圖,我想要為 React Native 做自動螢幕截圖。

React Native 目前還是處於開發活躍期,官方文件混亂、第三方文件少,有些功能還要自己跳下去爬原始碼才知道。本文所描述的螢幕截圖,基本上官方的文件一丁點都沒有提到,網路上的資料也是片片斷斷非常稀少,讓我排列組合了一整天才有了今天的成果。

假設

  1. 假設你使用 React Native 開發 app,而且是 iOS app。
  2. 假設你採用 fastlane 自動化開發流程,想要使用 snapshot 做自動截圖。
  3. 假設你懂一點的 Swift。 (你也可以使用 Objective-C,但要自己轉換)

前置作業

在放置 fastlane 資料夾那層目錄執行 snapshot init,在 fastlane 目錄產生 Sanpfile 與 SnapshotHelper.swift。我自己是放在 React Native 的專案根目錄,因為之後預計也會讓 Android 使用 fastlane 進行開發流程自動化。

解法

1. Snapshot 目前只支援透過 UI testing 做螢幕截圖

首先,在 iOS 上,Snapshot 目前只支援透過 UI testing 做螢幕截圖。

UI testing 是 for Mac OS X and iOS app 的 UI 測試框架,提供點擊、滑動等 API,讓 Xcode 模擬使用者的行為。透過 UI testing + Snapshot,你可以做到 進入某個畫面、先滑動一次之後再截圖 這種非常精細的擷取操作。

所以如果要讓 Snapshot 做自動截圖,你勢必要 讓 UI testing 有辦法串接到 React Native

2. 透過 testID property 為 React Native component 命名

testID string

Used to locate this view in end-to-end tests. NB: disables the ‘layout-only view removal’ optimization for this view!

- 擷取自 React Native 官方文件

官方文件完全看不懂在寫什麼。

Notice that I added testID=”test-id-textfield” and testID=”test-id-textfield-result” to the TextInput and the View. This causes React Native to set a accessibilityIdentifier on the native view. This is something we can use to find the elements in our UI test.

- 擷取自 Automated UI Testing with React Native on iOS

這篇文章的解釋好多了。

簡而言之,為 component 標上 testID,React Native 在編譯之後會為轉換出來的 native component 設定 accessibilityIdentifier 的屬性 (它是一個 Optional String),這個屬性你可以類比成 HTML5 DOM 的 id,在同一份網頁中 (在 React Native 則是 同一隻 app 中) 是一個全域的 *identifier*,也就讓 Snapshot 在 UI testing 時可以透過這個 identifier 鎖定特定的 React Native component 了。

3. 等待 UI component 出現在畫面上

React Native 由於程式語言採用 JavaScript,非同步的操作寫起來非常爽 (除去 callback hell 這些不好的回憶),結果有時候爽過頭,在測試的時候忘記 component 是非同步的,有時候測試會過有時候又不會過,實在讓人丈二金剛摸不著頭緒。

UI testing 提供 等待 UI component 出現在畫面上、但如果等太久就讓測試失敗 的 API。

let nextGame = self.app.staticTexts["Game 4 - Tomorrow"]
let exists = NSPredicate(format: "exists == true")
expectationForPredicate(exists, evaluatedWithObject: nextGame, handler: nil)

app.buttons["Load More Games"].tap()

waitForExpectationsWithTimeout(5, handler: nil)
XCTAssert(nextGameLabel.exists)

Here we create a query to wait for a label with text “Game 4 — Tomorrow” to appear. The predicate matches when the element exists (element.exists == true).

We then pass the predicate in and evaluate it on the label. Finally, we kick off the waiting game with waitForExpectationsWithTimeout:handler:. If five seconds pass before the expectation is met then the test will fail.

- 擷取自 UI Testing Cheat Sheet and Examples

這兩個 API 就是 expectationForPredicate 與 waitForExpectationsWithTimeout,具體的用法如擷取文字所示。如果要配合 React Native,寫出來的樣子就會如下所示:

1. 在 React Native 裡為 View 標上 testID

<View testID={'name-me-whatever-you-like'}>
<TouchableWithoutFeedback onPress={onPressHandler}>
...
</TouchableWithoutFeedback>
</View>

2–1. 在 UI testing 中存取

class APPTests: XCTestCase {
let app = XCUIApplication()

override func setUp() {
super.setUp()
continueAfterFailure = false
setupSnapshot(app) // 啟用 Snapshot
app.launch()
}

func testAScreen() {
let touchable = app.otherElements["name-me-whatever-you-like"]
touchable.tap() // 模擬點擊
snapshot("0AScreen") // 截圖!
}
}

snapshot(“0AScreen”) 執行之後的截圖就是該 View 被使用者點擊之後的畫面。

2–2. 等待 component 出現之後再截圖

有時候 component 可能是某個非同步操作完成之後才會出現在畫面上,這個時候就需要剛剛提到的兩隻 API 輔助了。

class APPTests: XCTestCase {
let app = XCUIApplication()
let exists = NSPredicate(format: "exists == true")

override func setUp() {
super.setUp()
continueAfterFailure = false
setupSnapshot(app) // 啟用 Snapshot
app.launch()
}

func testAScreen() {
let touchable = app.otherElements["name-me-whatever-you-like"]

// 等 touchable 出現在畫面上,最多等 5 秒,不然就 test fail
expectationForPredicate(exists, evaluatedWithObject: touchable, handler: nil)
waitForExpectationsWithTimeout(5, handler: nil)

touchable.tap() // 模擬點擊
snapshot("0AScreen") // 截圖!
}
}

Snapshot 執行完之後就會建立一個 HTML 文件,展示所有在 UI testing 執行的過程中擷取到的螢幕截圖。這個文件在之後 fastlane Deliver 會使用到,但你可以把它放進 .gitignore,因為每次需要上傳截圖之前理論上都會跑一次 Snapshot 更新截圖。

結語

React Native 不是 JavaScript,他只是語言採用 JavaScript 來加速開發。

在本篇文章中,我們用到了不少 Swift 的知識與語法去實踐自動化螢幕截圖的功能,所以即使是採用 React Native,如果你是 native app 開發出身的工程師,也絕對不能忘記原來的背景知識 (e.g. Cocoapods、Objective-C、Swift、Java、Gradle…等)。因為 React Native 的資源還非常非常少,很多時候我們必須自己動手去橋接 native modules 給 React Native 使用,幸好 React Native 將橋接這件事情做得非常簡單,基本上只要介面 (Objective-C 的 @interface)寫好,剩下的事情就交給 React Native 去完成了。

2. 在 React Native 裡避免大量的 ../../

問題

React Native 採用 ES6 做為開發語言,而 ES6 的 import 語法指定檔案時採用相對路徑。如果採用 Redux 架構開發,很快地 Component 就會開始出現以下一堆亂七八糟的 import 語句。

import DoSomething from '../actions/DoSomething';
import DoAnotherThing from '../actions/DoAnotherThing';

如果是從 HTML4 時代就開始寫 JavaScript 的工程師,乍看之下沒有什麼不好,因為絕對路徑與相對路徑的好壞在 HTML4 的時代就吵過一次,最後的結論也都是有好有壞。*但 React Native 不是 ES5*,規模很容易膨脹到超過傳統 ES5 專案的程度,因此應該另尋專為大型專案設計的解決方法。

他山之石

讓我們來看看 Python 與 Java 的 import 語句。

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

from app import MyApp
import butterknife.BindView;
import butterknife.BindViews;
import butterknife.ButterKnife;
import butterknife.OnClick;
import butterknife.OnItemClick;
import butterknife.OnLongClick;

import tw.com.sudo.JavaProject.DummyClass;

Python 跟 Java 都號稱專為大型專案設計,而他們的 import 語法也驚人地相似。簡而言之,它們原理就是 以專案目錄為起點,去定位被 import 的檔案位置。因此無論你的檔案怎麼搬動,只要還在專案目錄下,import 語句的起點都不會改變,讓重構的意願提高。

解法

如果是 React Native,*你什麼事都不必做*,因為 React Native 已經內建了從專案目錄計算 import 路徑的功能。雖然 React Native 已經支援了相關功能,但由於官方文件並沒有明確說明,只有 相關的 issue,具體的方法是在 StackOverflow 上找到的,因此如果要使用在 production 上要特別小心。

如果你要嘗鮮,以下是具體寫法。

// src/foo/DummyClass.js
class DummyClass { ... }
export default DummyClass;
// src/package.json
{
"name": "@src"
}
// src/components/Bar.js
import DummyClass from '@src/foo/DummyClass';

整體而言就是透過 package.json 去為 package.json 的目錄命名 (在此命名為 @src,越特別越好,不然可能會與其他來自 npm 的 package 撞名),之後所有在專案下的 ES6 檔案的 import 路徑就會多一個以 @src 開頭的路徑,起點從 src/ 開始。

結語

掰掰,../../。

--

--