Kategoriak: All - 方法 - 測試 - 工具

arabera 余小章 's 6 years ago

731

BDD Workshop

測試是軟體開發中的關鍵部分,能確保程式碼在需求變更或維護時保持穩定。測試方法包括單一測試和多個測試,使用熱鍵如Ctrl+R, T來執行單一測試或Ctrl+R, A來執行所有測試,有助於提高測試效率。測試的類型多樣,包括比對、驗證,並使用工具如Fluent Assertions進行部分欄位比對。良好的測試程式碼應具備自動化和可重複性,這樣可以減少手動測試的繁瑣操作和時間浪費。

BDD Workshop

BDD Workshop

導入

補測試的時機
不知錯誤在那,該如何進行下一步:這表示一次貪心實作太多功能,把目標縮小,一步步補 unit test 吧。
執行 debugger:同上,你還是得花時間設中斷,一步步看訊息,不如寫個 unit test,之後能持續受惠。
在程式裡輸出訊息找錯誤:與其花時間寫 print、看輸出訊息、再回頭砍掉 print,不如寫個 unit test,之後能持續受惠。
我發現以下幾個情況,都是寫新測試碼的好時機:
甚麼情況下不要寫測試
無法預測結果的

非對稱式加密

圖片

亂數

Survey API、POC
以下情境不要寫測試
寫測試也是需要成本的,不是甚麼都能寫
不要測,好爽,是哪個案子,我要加入。
不會課聽完,你就不會卡關
從你自己開始跨出第一步

Pickles

https://dotblogs.com.tw/yc421206/2016/04/27/specflow_pickles_live_document_command_line
https://dotblogs.com.tw/yc421206/2016/04/25/specflow_pickles_live_document

SpecFlow

測試總管 Output 查看錯誤
忽略不執行 Ignore
限制、過濾 Scope
執行順序 Order [BeforeScenario(Order = 0)]
API
Table轉型

table.CreateInstance

table.CreateSet

Table比對

Table.CompareToSet(collection)

Table.CompareToInstance(row)

using TechTalk.SpecFlow.Assist;
資料容器

目的是用來共享不同步驟的資料資料

FeatureContext和ScenarioContext不同的之處在於物件生命週期

ScenarioContext.Current.Get("key")

ScenarioContext.Current.Set(value,"key")

Binding

1 - [BeforeTestRun] => static method 2 - [BeforeFeature] 3 - [BeforeScenario] or [Before] 4 - [BeforeScenarioBlock] 5 - [BeforeStep] 6 - [AfterStep] 7 - [AfterScenarioBlock] 8 - [AfterScenario] or [After] 9 - [AfterFeature] 10 - [AfterTestRun]

Hook靠著 [Binding] 來運作

[Binding]
public class Test{}

Scope

http://specflow.org/documentation/Scoped-bindings/

靠著描述來Binding測試方法,只要步驟的描述能Binding的到,就可以使用,Scope用來控制測試方法能給誰用

Bindings在有Specflow專案例面是一個全域物件

參數

多欄多筆資料table

Table可以轉成強型別物件

[Given(@"the following books")] public void GivenTheFollowingBooks(Table table) { ... }

Given the following books |Author |Title | |Martin Fowler |Analysis Patterns | |Gojko Adzic |Bridging the Communication Gap |

工具自動產生只是輔助,最終你還是要改成你需要的型別

Feature描述中用雙引號包起來,工具所產生的測試方法會帶有字串參數;用數字會產生int

Scenario: View last incidents Given the user "Linda" exists And I log in as "Linda" When I go to the incident page

Hook的描述用(.*),則代表參數

[Given(@"the user (.*) exists")] public void GivenTheUserExists(string name) { // ... }

步驟靠Hook來Binding後端的測試方法
Given、When、Then,代表一個步驟
Feature
Scenario Outline: authentication Given 我輸入 , When 我按下Login Then 結果應為 Examples: | UserId | Password | Result | | kobe | 12234 | false | | yao | 1234 | true | | jordan | 5566 | false |
Scenario: 模糊查詢產品 Given 我輸入查詢資料 | ProductName | | 余小章's | And 資料已有 | ProductName | UnitPrice | Discontinued | | 余小章's C# book | 29 | false | | 余小章's VB book | 21 | false | | 余小章's ASP.NET book | 22 | false | When 我按下查詢 Then 查詢結果應該有 | ProductName | UnitPrice | | 余小章's C# book | 29 | | 余小章's VB book | 21 | | 余小章's ASP.NET book | 22 |
使用者故事,案例、場景
組態
unitTestProvider=MsTest

Cucumber/Gherkin

https://cucumber.io/

Given => Arrange

When => Act

Then => Assert

Scenario: 新增一筆會員資料 Given 前端應傳來以下MemberViewModel資料 When 調用MemberWorkflow.Insert Then 預期資料庫的Member資料表應有以下資料 And 預期資料庫的MemberLog資料表應有以下資料
Given => Arrange When => Act Then => Assert 每一個關鍵字就是一個步驟
Feature: 標題(描述故事的單行文字) As a [WHO 角色:誰要使用這個功能] I want [WHAT願望:需要完成甚麼樣的功能 ] So that [WHY利益:為什麼需要這個功能]

行為驅動開發

Behavior-driven development,BDD

需求描述
Scenario

Scenario 1: 標題(描述場景的單行文字) Given [上下文] And [更多的上下文]... When [事件] Then [结果] And [其他结果]

用來驗證故事,Scenario都完成了,才代表功能完成

User Story

Story: 標題(描述故事的單行文字) As a [WHO 角色:誰要使用這個功能] I want [WHAT願望:需要完成甚麼樣的功能 ] So that [WHY利益:為什麼需要這個功能]

用極度抽象的方式來描述文件

描述使用者、系統或軟體購買者,有價值的功能

使用者故事,系統對某方面的功能稱為"故事"

開發者和使用者使用同一種語言來描述系統,避免表達不一致所帶來的問題
用人話(通用語言)來說明需求、描述使用案例
BDD是TDD的進化開發方法,使用通用語言定義系統的需求(行為)
TDD的價值是需求,把需求從程式碼裡面抽取出來,BDD的實作方式就被提出來

測試驅動開發

Test-driven development,TDD


LifeCycle
測試就是需求,需求就是測試
依據『需求』先寫Test Code,然後再寫Production Code
不是測試方法,是一種開發方法

開發技巧

3A 原則

驗證結果

Act

調用目標

Arrange

準備前置作業

驗證
Mock
Stub
隔離

解除耦合、降低相依

NSubstitute

NSubstitute

Mock Framework

用來動態建立模擬物件,然後注入目標

必須是 interface 或 virtual

必須是 interface 或 virtual

用來動態建立模擬物件,然後注入目標

Mock Framework

DI

DI (Dependency Injection)實現的方式

欄位

屬性

方法

建構函數

委派

介面

檔案


Test

InternalsVisibleTo

DI (Dependency Injection)實現的方式

用在測試

讓特定的專案(Component),可以存取 internal 的成員

欄位 屬性 方法 建構函數 委派 介面 IO(檔案、SQL)

DI就是實現IoC的技巧

DI (Dependency Injection)依賴注入

IoC

IoC (Inversion of Control)

反轉控制

由外部決定物件生成的方式

SOLID的D,依賴倒置原則 (D.I.P)

SOLID的D,依賴倒置原則 (D.I.P)

由外部決定物件生成的方式

IoC (Inversion of Control)反轉控制

Data Base

localdb
安裝快速
隔離線上資料庫
SQL Project
搭配CI,可自動化部署或產生差異腳本
資料庫結構或資料版控
以SQL專案為主進行管理

DBA會不習慣

文件
常用
Exception
ShouldAllBeEquivalentTo
ShouldBeEquivalentTo
用來比較多個欄位
擴充方法
2月份改版

AutoMapper

載入對應
非全域對應

mapper.Map

var mapper = config.CreateMapper()

var config = new MapperConfiguration

全域對應

Mapper.Map

Mapper.Initialize

實作 Profile,集中管理

MsTest

其他
InternalsVisibleTo
TestCategory
Ignore
測試總管
測試方法排序
過濾特定的測試方法
列出所有測試方法,若沒有看到測試方法,要先Build
比對、驗證
部分欄位比對

匿名型別

Fluent Assertions

ExpectedException

不要在測試程式寫catch唷

CollectionAssert
object.Equals

object.ReferenceEquals(actual, actual)

object.Equals(expected, actual)

Assert

Assert.AreSame(expected,actual) => 兩個物件是否同一個物件

Assert.AreEqual(expected,actual) => 兩個物件是否相等

熱鍵
在方案內的任一地方

Ctrl+R, Ctrl+A 偵錯所有測試

Ctrl+R, A 執行所有測試

滑鼠屬標停在TestMethod區段內

Ctrl+R, Ctrl+T 偵錯單一測試

Ctrl+R, T 執行單一測試

Hook 生命週期

1 - Runs [AssemblyInitialize]

2 - Randomly runs a [ClassInitialize]

3 - Runs the class [TestInitialize]

4 - Randomly runs a [TestMethod] from that class

5 - Runs the class [TestCleanup]

Repeat 3 through 5 for each TestMethod in the class

Repeat 2 through 5 for each test class

6 - Runs all classes [ClassCleanup] methods

7 - Runs [AssemblyCleanup]

1 - [AssemblyInitialize] => static method 2 - [ClassInitialize] 3 - [TestInitialize] 4 - [TestMethod] from that class 5 - [TestCleanup] 6 - [ClassCleanup] 7 - [AssemblyCleanup]
Hook靠著 [TestClass] 來運作

[TestClass]
public class Test{}

Architecture

3 Layer 缺點
分層無法封裝所有的東西,例如因應需求要在前端介面增加 輸入欄位,必須要在表現層、商業邏輯層、資料存取層都做 相對應的修改
不合理的分層使得不同層次的程式碼耦合在一起,相依性增加,使得程式越來越難以隨著需求變更而進行相應調整
不合理的分層將會導致軟體開發品質下降
過多的分層會影響效能
3 Layer 優點
平行開發

各層之間具有一定的"獨立性",維持之間溝通的介面不變,各層可以根據具體問題各自實作,而不需要其他層也必須做出相應調整

IO瓶頸

網路

記憶體

提高延展性

分流

資源不足時,用Web Service/Web API掛載

提高可測試性

輕易的模擬依賴的物件

容易寫測試

提高可維護性

合理的分層有助於分工開發與維護,哪一層壞了就改哪一層

可以將各層次間的相依性減到最低

提高可抽換性

分支主題

以替換某一層的具體實現,只要前後提供的服務相同即可

3 Layer 職責
Value Object

各Layer之間的資料模型定義

DataAccess Layer

資料倉儲

Text

Excel

SQL

I/O

Repository/Data Access/Persistence/PO/DAO

存取資料

Business Logic Layer

商業邏輯,流程控制

Presentatioin Layer

展現層,使用者操作介面,介面的連動效果

3 Layer架構圖
物件導向六大原則:SOLID
D:依賴反轉原則,抽象化。(Dependency Inversion Principle)

細節應依賴抽象

抽象不應依賴細節

高層模組不應依賴低層模組,兩者都應依賴其抽象

I:介面隔離原則,介面要特定目的、易懂、可再用性高。(Interface Segregation Principle)

客戶端不應該依賴他不需要的介面,類別間的依賴關係應該建立在最小的介面上。

L:最少知識原則,(Least Knowledge Principle, LKP),又稱狄米特法則(Law of Demeter, LoD)

一個物件應對其他物件有最少的了解。通俗來說就是我只要知道你提供這麼多public方法,其他我都一概不關心。

L:里氏替換原則,類別間的相容性。(Liskov’s Substitution Principle)

父類別能出現的地方,子類別就可以出現,而且替換成子類別不會造成錯誤或異常。

O:開放封閉原則,開放擴充、封閉修改。(Open-Closed Principle,OCP)

應該透過擴展來實做變化,而不是修改已有的程式碼來實作變化

S:單一職責原則,單一類別單一責任、低耦合 (Single Responsibility Principle,SRP)

用“職責”或“變化原因”來衡量介面或類別設計,也可以套用到方法

關於測試

開發人員最討厭的事
當你學會用使用案例當開發技巧,你還會再多討厭一件事
文件和程式對應不起來
沒有文件
使用案例能取代以往的文件嗎
可用來補足以往文件的不足
不能
為什麼用使用案例會減少整體開發時間
釐清需求減少浪費
寫使用案例是為了減少你的測試時間用的,減少測試時間進而減少整體開發時間,那你為什麼不做呢?
用使用案例來開發的好處
提升開發人員設計物件的能力

寫測試的重點是在於隔離技巧(mock),這技巧學會了,設計物件及架構的能力無形之間就會提升

提升軟體的品質
測試程式可以變成可交付的文件
釐清需求問題,減少浪費,縮短開發時間
功能(目標),團隊能有一致的共識,不會各自表述
就是一種使用範例
縮短找Bug的時間,快速地找出問題
需求異動或改善系統的時候,不怕系統被改壞
確保程式真的能動
使用案例
要有三個東西

驗證執行結果

執行步驟

輸入資料

不要用臆測的方法來除錯
用使用案例來取代
你以為你在開發,其實是在創造臭蟲...
測試作業太慢開始、程式碼之中散佈著因為需求或設計規格、或實作結構問題所產生的一些疏漏或錯誤,這些現象使得程式瑕疵擴散或蔓延的速度比修正的速度還來得快。當程式的瑕疵愈變愈多、除錯就會顯得更困難,對程式的除錯與測試都是個沉重的負擔,更不用說可以及早發現程式中的瑕疵了。
有些開發者會習慣於暫緩程式的驗證,他們總以為進行程式的開發遠比進行測試驗證還來得要有生產力,甚至在面對時程的壓力時,會選擇省略功能測試,期望以整合測試或系統測試來將測試問題延後處理
寫好的功能,沒有使用案例,很快就忘記這個功能是幹嘛用的
自己創造Bug,自己解開Bug
你誤會了,F5是偵錯,不是測試
一個好的測試程式碼有幾個特性

結果是穩定的,不會因為環境而影響結果

任何人、任何地點都能運行

能自動化,可重複執行

以前的測試方法做不到
思考一下,既然測試都是要做的事,這樣的方法不但沒有效率,還會影響品質
你需要用多少時間知道哪些程式被影響了?
用這樣的方法你知道程式碼異動後影響的範圍嗎?
上述的動作都是憑開發人員的心情、直覺,只有開發人員當下的狀態才知道,日後沒有人知道開發人員怎麼做,包含開發人員自己也沒辦法在短時間之內知道
其實你一直都在寫測試
需求異動時

結果,A功能改好了,但BCD功能壞掉了,可怕的是BCD壞了你還不知道....

好花時間、好麻煩阿,最後,只針對那個需求按A按鈕

把UI打開,將所有的按鈕都按過一遍,下次需求異動的時候,在手動按過一遍

開發、維護時

對表單的CUD操作,會開啟SSMS,針對需求下Insert、Update(還原)、Select(人眼比對),存檔,然後下次就忘了這段Script在是幹嘛的,然後再重寫一次..

在UI彈跳出一個警告視窗,觀看視窗的結果,看完了(人眼比對)再把它註解起來

為什麼覺得不容易執行
測試VS不測試
主管不挺

既然要測,為何不挺?

寫好的東西要不要測?

不知道如何開始
心理障礙,不習慣改變、害怕、質疑
測試寫了,還是沒有辦法滿足使用者的需求
不寫測試的推託說詞
這個功能很簡單還需要寫測試嗎

只要有Production Code,就應該被驗證,而不是用簡單與否來當旗標

最有效確認需求的方法之一就是利用測試個案. 就像是在測試一個系統一樣, 來測試需求是否正確. 也就是說, 你可以用範例來闡釋需求, 把範例變成測試, 然後使用測試驗證需求」

為了避免浪費,必須要做需求確認

一個簡單的需求,大家的想法就不一樣..

但其實是,最簡單的事往往最花時間

寫測試會讓專案Delay

測試的重點不是寫測試,而是需求

需求不明確,幹嘛要花時間寫測試

為什麼需求不明確就要開始寫程式?而不是先釐清問題?

因為,寫了測試還是沒辦法達到User需求阿,幹嘛寫它

你可能搞錯對象了

功能都寫不完了,沒有時間寫測試

開發人員的痛點

....
到處都有ALLEN留下的代碼
無法評估這次的異動會影響那些
上線前一刻才發現問題很多
要花好多時間找錯誤
維護前人的專案,不安感很重
需求一直改
用正確的心態面對異動
Ruddy老師說過的話,一位信仰敏捷的人,是會認清這個事實: 「你無法凍結需求,正如你無法凍結市場、競爭、知識、進化或者成長一樣。」
改A壞B