Test and Automation === - [Code Refactoring](#Code-Refactoring) ## Type - Unit testing - Solitary - Sociable - Component Testing - Integration testing - System testing - Interface Testing - Install Testing - Recovery Testing ## Unit Testing ### Unit - unit of code - unit of behavior ### FIRST - Fast - Independent - Repeatable - Self-Validating - Timely ### 3A - Arrange - Act - Assert ### Story #### 教練 ~ 我想寫測試... ```java= public class Story { public void start() { // 某人根據上頭的指示, . . . // 準備了一些物資放進背包, . . . // 尋找出口並開鎖離開, . . . // 將自己的經歷紀錄於日誌上 . . . } } ``` #### 整理程式碼 ```java= public class Story { public void start() { // 某人 // 詢問上頭指示 // 根據指示準備物資放進背包 // 尋找出口 // 開鎖離開 // 將自己的經歷紀錄於日誌上 } } ``` #### Extract Function ```java= public class Story { public void start() { function 上頭指示 詢問上頭() {...} function 背包 準備物資() {...} function boolean 從出口離開() { // 尋找出口 // 開鎖 // 離開 } function boolean 紀錄於日誌上() {...} // 某人 上頭指示 = 詢問上頭(); 背包 = 準備物資(); 成功離開 = 從出口離開(); 某人的經歷 = ...; 紀錄於日誌上(); } } ``` ```java= public class Story { public void start() { // 某人 上頭指示 = 詢問上頭(); 背包 = 準備物資(上頭指示); 某人.攜帶(背包); 成功離開 = 從出口離開(某人); 某人的經歷 = ...; 紀錄於日誌上(某人的經歷); } private 上頭指示 詢問上頭() {...} private 背包 準備物資(上頭指示) {...} private boolean 從出口離開(某人) { // 尋找出口 // 開鎖 // 離開 } private boolean 紀錄於日誌上(某人的經歷) {...} } ``` #### Extract Class ```java= public class Story { class 上頭聯絡窗口 { public 上頭指示 詢問上頭() {...} } class 物資塔 { public 背包 拿取物資(物資列表) {...} } class 異次元空間 { public boolean 從出口離開(人) { 出口 = this.尋找出口(); this.開鎖(出口); return this.離開(人, 出口); } private 出口 尋找出口() {...} private boolean 開鎖(出口) {...} private boolean 離開(人, 出口) {...} } class 日記本 { public boolean 紀錄(某人的經歷) {...} } private final 上頭聯絡窗口 秘書子; private final 物資塔 倉庫; private final 異次元空間 空間; private final 日記本 日記; public Story() { this.秘書子 = new 上頭聯絡窗口(); this.倉庫 = new 物資塔(); this.空間 = new 異次元空間(); this.日記 = new 日記本(); } public void start() { // 某人 上頭指示 = this.秘書子.詢問上頭(); 背包 = 倉庫.拿取物資(上頭指示.to物資列表()); 某人.攜帶(背包); 成功離開 = 空間.從出口離開(某人); 日記.紀錄(某人.經歷()); } } ``` ```java= public class 上頭聯絡窗口 { public 上頭指示 詢問上頭() {...} } public class 物資塔 { public 背包 拿取物資(物資列表) {...} } public class 異次元空間 { public boolean 從出口離開(人) { 出口 = this.尋找出口(); this.開鎖(出口); return this.離開(人, 出口); } private 出口 尋找出口() {...} private boolean 開鎖(出口) {...} private boolean 離開(人) {...} } public class 日記本 { public boolean 紀錄(某人的經歷) {...} } public class Story { private final 上頭聯絡窗口 秘書子; private final 物資塔 倉庫; private final 異次元空間 空間; private final 日記本 日記; public Story( 上頭聯絡窗口 秘書子, 物資塔 倉庫, 異次元空間 空間, 日記本 日記 ) { this.秘書子 = 秘書子; this.倉庫 = 倉庫; this.空間 = 空間; this.日記 = 日記; } public void start() { // 某人 上頭指示 = this.秘書子.詢問上頭(); 背包 = 倉庫.拿取物資(上頭指示.to物資列表()); 某人.攜帶(背包); 成功離開 = 空間.從出口離開(某人); 日記.紀錄(某人.經歷()); } } ``` ![image alt](https://user-images.githubusercontent.com/88981/52933895-c0d47600-338f-11e9-9034-11e1ad0c42f1.gif) ## Integration Testing - Prepare data - Act - Assert . . . . . . . . . . . . . . . . . . . . . . . . . ![image alt](https://yu-jack.github.io/images/unit-test/modify-unit-test.gif) ## Code Refactoring > Argument from Ignorance > > -- from Lander University - Philosophy 103: Introduction to Logic 一些些技巧: [Refactoring Techniques](https://refactoring.guru/refactoring/techniques) Transaction Management: - Two Phase Commitment Protocol - TCC - SAGA - OutBox > If you are actually able to build a well-structured monolith, > you probably don’t need microservices in the first place. > > from [Don’t start with a monolith](https://martinfowler.com/articles/dont-start-monolith.html) > [name=Stefan Tilkov] ## 簡單暴力 vs 抽象化 ### 簡單暴力 ```java class Main { void main() { var customer = new Customer("Bill"); var order = Order.create(customer, "Coffee"); var staff = new Staff(9527); var cashier = new Cashier(); // 結帳 order.setStaff(staff); staff.setCashier(cashier); staff.setOrders(order); cashier.addOrder(order); // 泡咖啡 var cup = new Cup(); staff.setCup(); cup.setFilterCone(new FilterCone()); cup.setCoffeeGround(new Coffee()); staff.brew(cup); staff.wait(); staff.setFilterCone(null); // 送餐 staff.setCoffeeTo(customer); customer.setCoffee(order); } } ``` ### 抽象化 ```java class Main { void main() { var barista = new Barista(9527); var customer = new Customer("Bill"); var order = customer.placeOrder("Coffee"); barista.processPayment(order); barista.make(order); barista.serveOrderTo(order, customer); } } ``` ### 有啥差別 ??! |項目\方法|簡單暴力|抽象化| | --- | --- | --- | | 資料操作 | Must | Must | | 主詞 | | Must | | 主詞的動作 | | Must | | 行為 | Optional | Must | ```java class 簡單暴力 { void main() { // 到 DB 拿資料 // call API // 因為資料相依的關係, 有特定行為 // 塞 DB } } class 抽象化 { void main() { // 從 DB 拿資料, 轉換成`主詞` // call API // 主詞做出某個動作 (ex: 人陷入飢餓狀態) // 兩個主詞之間有相依的行為 (ex: 人對門做出打開行為 (人打開門) or 門被人打開) // 塞 DB } } ``` #### 神奇小問題 商務邏輯除了 CRUD (不管是單獨的 Service or 別人家的 Service), 還有啥 ? (ex: 驅動硬體做某事: 發送 SMS, etc...) 諸位認為以下操作在都符合商務邏輯的情況下, 有何不同: - 一個大 function 解決全世界 - 因為要做的事太多, 所以 call function 輔助解決一些事情 - 因為要做的事太多, 所以掛 side car 進來給 call 輔助解決一些事情 - 因為要做的事太多, 所以 call by HTTP 輔助解決一些事情 - 因為要做的事太多, 所以 call by gRPC 輔助解決一些事情 - 因為要做的事太多, 所以 publish an Event 請 subscriber 輔助解決一些事情