1. 測試什么:
1) 測試輸出結果,測試返回結果的正確性
2) 測試邊界條件
格式
順序
范圍
外部條件
值的存在,特殊值,null,0
時間
3) 測試行為,內在邏輯關系,驗證相反的邏輯關系
4) 相互校驗,反復核對,多種方式驗證
5) 考慮所有可能出錯的條件,模擬錯誤情況
6) 性能差,壓力大,吞吐率小,響應率低等特殊情形
2. 良好測試的屬性
1) 自動化
2) 全面
3) 可重復
4) 獨立
5) 高質量,和產品代碼一樣對待
3. 何時運行測試
1) 寫了一個新的方法,功能
2) 修了一個bug
3) 成功編譯,修改了編譯錯后
4) Check in之前
5) 持續集成
4. Review測試代碼,
1) 在寫業務代碼之前, TDD
2) 寫好業務代碼之后,業務代碼和測試代碼要一起review
5. 測試已有的代碼
1) 代碼中頻繁出現問題的部分
2) 新添加的代碼邏輯
6. Design
1) 關注點分離,使可測試的邏輯分離出來,也方便寫出獨立的測試
2) 弄清楚業務邏輯中的規律性,對這種規律進行測試
3) TDD,改善已有的接口
4) 驗證輸入,輸出
5) 先寫測試,TDD,便于寫出可測試的代碼
6) 設計要考慮可測試性
7) Test程度:也不能過度test,破壞了class的封裝性,也不能為了test使軟件過度松散,當然也不能濫用mock object,這會使得test不夠友好,重構變難。可以對一些public的interface進行測試,然后對類的行為進行測試。
8) 表達意圖:測試就是文檔
9) 不要修改SUT (system under test), 如果我們使用了Test-Specific子類,要保證我們沒有修改需要驗證的業務邏輯,可能只是修改一些可訪問性或者注入一些間接的輸入。
10) 保持測試的獨立性
11) 隔離SUT,使SUT和其他軟件或者非測試部分無關,可以用依賴注入或者依賴lookup的方式輸入test specific子類。使得測試不受其他部分影響。
12) 最小化測試的重疊部分
13) 最小化不可測試的代碼,比如遇到GUI,多線程,要把可測試的代碼分離出來
14) 使測試邏輯和產品代碼邏輯分開,盡量不使用test hook
15) 將測試代碼和產品代碼同等對待
7. 測試陷阱
1) 以前的測試有問題沒關系(代碼重構后),測試不是產品代碼不重要
2) Smoke test, assertTrue(true),無assert
3) 只能在某個或者某些特定的環境中跑通,比如本地standalone,windows等等
4) 浮點數問題
5) 測試要跑很長時間
6) 測試老是需要改動,可能代碼強耦合,需要重構
7) 需要經常的debug
8) Test有時跑的通,有時跑不通,是不是用了隨機數,產生不符合規范的測試數據, 環境是否有依賴性?
9) 測試不夠自動化,經常需要手動干預
10) 測試代碼的維護很困難:測試代碼寫的不夠專業等等
11) 測試的時候沒有問題,上了產品出現問題:測試是不是很久才跑一次?環境是不是有依賴?
8. 如何寫一個測試
新代碼
TDD

寫一個失敗的測試
編寫代碼讓這個測試可以跑通
重復進行第一步和第二部
在這個過程中,主動地進行重構
當沒有新的測試可以加入,所有測試也都跑通了,此時也就完成了
修改一個bug
找出bug.先
寫一個失敗的測試,標識出bug的存在
修改代碼直至測試跑通
檢驗是否所有其他測試是否都能跑通,表示沒有影響其他的代碼邏輯
9. 測試依賴環境的setup 和 teardown
Fresh fixture: 每個test方法執行都會setup和teardown
Shared fixture:可以為多個test方法,test case使用,一般用于那種創建開銷很大容易導致slow test的資源
Minimal fixture
Standard fixture
Setup:
· Test方法里自己構建,minimal fixture
· Setup方法:standard fixture
· Test utility,create方法
· Lazy loading,只能用于那些不需要teardown的
· Shared fixture,可以預先通過back door的方式準備好,可以用class static屬性預先創建,可以用lazy load,可以用junit 4的@before, @after,甚至可以用test chain(個人認為不推薦,test之間存在依賴和交互,不夠獨立)
Teardown
Fresh fixture, 通常在teardown方法里實現,可以借助于test helper,基類。
如果是某個test方法特有的,可能需要在該test方法里的finally里實現,也可以借助于test helper。
10 Verification(assertion)
1) State assertion: 驗證狀態,輸出。state assertion,
問題
· 相同的assertion如果經常重復出現在不同的test方法里?
可以用Expected object或者自定義的assertion來替換
· 如何增加assert的可讀性?
可以用Expected object或者自定義的assertion來替換(test specific assertion,比如test 方法中含有條件判斷邏輯,可以將這部分提取為自定義的assertion,從而保持測試代碼的簡潔和可讀性。
2) Behavior verification: 驗證交互,行為,間接輸出,不僅僅是直接的返回結果。Mock Object可以驗證和SUT之間交互的行為邏輯,或者用spy object判斷過程的間接結果
3) Delta assertion:用在有shared fixture時,但是不能解決并發多個test同時執行出現的沖突問題,可以解決另外一個test修改了shared fixture,接著跑run這個test出現錯誤的問題。就是在test run之前先獲得shared fixture的情況,run之后,驗證變化,不驗證絕對值。
4) Guard Assertion:將if 判斷用assertNotNull或者其他assertion語句替換
11. Mock對象的使用
Mock什么
Mock的是依賴組件不是被測試的單元
何時需要Mock
實際對象的行為是不可預測的
實際對象很難準備,初始化,啟動
實際對象的行為很難觸發,重現
實際對象的行為很慢
實際對象需要人工干預,有UI
需要觀察實際對象的內部行為
存在的實際對象是我們無法控制和觀察的,比如當我們需要獲取SUT的間接輸入時
分類:按功能分
Test Stub
SUT對實際對象的間接輸入有依賴性,test stub使得測試對SUT的間接輸入有控制能力。
Test Spy
超越test stub,它還可以捕捉SUT的間接輸出,由test方法來驗證。
Mock Object
超越test spy,它也驗證間接輸出,也可以模擬間接輸入,但是它的實現原理,使用方法和test spy是不同的。
Test stub,test spy,mock object都可以通過JMock生成,但test stub,test spy還可以通過hard code方式或者采取用戶可配置的方式
Fake Object
Fake Object以極其簡單的方式實現了實際對象的所有功能,它只是為test構建的。但是我們不會把fake object作為控制點和觀察點。主要原因是因為實際對象很難創建,或者太慢等等,并不是為了驗證它的間接輸出或者內部行為。比如用hashtable fake數據庫,內存數據庫,fake web service,我們可以通過依賴注入或者lookup的方式將fake object和SUT關聯起來,在實際使用中用實際對象替換
Dummy Object
只是為了湊足參數,dummy object什么都不干,但是區別于null,可能都是空實現,沒有值。SUT不會和dummy object有邏輯交互。
12. 如何訪問SUT的私有屬性或者測試它的私有行為
同樣的package名,存放于不同的folder下面
繼承SUT,test specific subclass
反射
把屬性或者行為改成public
13. 如何解決數據庫的依賴問題
Database sandbox
一人一個database,schema更改后,保持database的同步。這其實并不能完全解決由于環境問題而導致的測試的不穩定性,因為某個developer自己的test之間也有可能出現db使用沖突。
每個測試人員,開發人員,測試相關人員都有一個本地的數據庫,或者在服務器上都有一個虛擬機。使用一些輕量級的db或者in memory db。
不同的人配備不同的db schema,必須使用同樣的數據庫結構
使用共享的db,但是要保證不同的用戶只能修改自己私有的數據,不能修改共享的數據。
存儲過程的自動化測試
存儲過程開發人員用db編程語言編寫單元測試,并在db級別對其進行單元測試,這些測試是跑在db里的
不需要為寫測試再多了解一門開發語言
測試和SUT保存在一個地方
可以TDD
應用開發人員用應用編程語言編寫單元測試,并在應用級別對其進行測試,把存儲過程當成黑盒進行測試,DbUnit
如何teardown db資源
Truncate table
Transaction rollback