軟體品質

遺留代碼

單元測試

x

x

參考資料

去海邊

在意的是...

日常開發...通常我們先在乎的是

生產力、開發速度

我們在乎的觀點能成就什麼?

會失去什麼?

符合規格、滿足需求的產品 (快)

什麼是遺留代碼 (Legacy Code)

什麼是遺留代碼 (Legacy Code)

什麼是遺留代碼 (Legacy Code)

最流行的是

「他人遺留下的原始碼」

「舊版本的軟體中遺留下來的代碼」 

什麼是遺留代碼 (Legacy Code)

Code without Test

思考實驗 - 全員大逃走

什麼可以幫到新來的工程師們?

什麼可以幫到新來的?

規格書

Readme 文件

具可讀性、良好的程式碼

有意義的註解

改不動?怕改壞?

已加入測試的程式碼

軟體開發的本質

知識的獲取、創造與保留

軟體開發的本質

知識的分類

Problem vs Solution

See Link

軟體開發的本質

知識的獲取

Problem vs Solution

商業領域的理解 與 軟體開發與商業領域間的探索

軟體開發的本質

知識的保存

Problem vs Solution

可被執行的 Spec 與 貼近商業需求且乾淨的程式碼

軟體開發的本質

遺留代碼

知識獲取了,但沒有良好保存下來

知識的獲取與保留

所以他們對遺留代碼的定義是...

對用戶的需求滿足了,知識獲取了,但沒有很好保存

遺留代碼的改動成本

遺留代碼的改動成本

越後期改動程式碼的時間成本越高

遺留代碼的改動成本

我們在乎的觀點能成就什麼?

會失去什麼?

符合規格、滿足需求的產品 (快)

已獲得的知識、未來的生產力

怎麼讓成本降低?

軟體架構層面

軟體測試層面

組織架構層面

從軟體架構層面

高內聚、低耦合

高內聚

高內聚

分開放,要剪紙時超難找啊...

低耦合

做什麼事都要用到它...

低耦合

頂多零件被兒子的車車用完

兒子跑去拆你的車叫做 side effect

低耦合(再多、稍微難用

高內聚、低耦合

所以 91 的單元測試課上了什麼

1. 從一個全新的需求來探索

2. 幫一個既有的功能加測試

1. 從一個全新的需求來探索

有一個服務是,當今天是情人節時...

祝福使用者情人節快樂

1. 從一個全新的需求來探索

實例化的需求 - 減少溝通成本

測試讀起來可以貼近商業需求 - 減少溝通成本、保存商業知識

(消滅遺留代碼)

1. 從一個全新的需求來探索

撰寫測試 (描述需求)- 助於需求討論、將需求釐清明確

開發

重構開發 (解除依賴)

重構測試 - 更妥善保存知識,隱藏無關的資訊、凸顯意圖

(增進可讀性)

流程

1. 從一個全新的需求來探索

增添新功能時,不確定有沒有 bug?

可透過添加測試來驗證、確保

2/14 情人節快樂?

2/12 情人節快樂?

3/14 情人節快樂?

4/14 情人節快樂?

品質呢?

1. 從一個全新的需求來探索

仍須透過 Code Review 把關

品質呢?

1. 避免低級錯誤

2. 驗證想的跟寫的是否一樣

 (大文豪 Ken 胡適:我手寫我口)

因為...測試

品質呢?

但想的 & 寫的一樣...也不一定代表正確

1. 從一個全新的需求來探索

怎麼寫個好棒棒的測試

Given

When

Then (Should)

Gherkin (Cucumber)

讓測試更好讀,更自然的英文語意


  givenToday('2021-02-14');
  shouldBeValentinesDay(holiday.checkValentinesDay());

凸顯意圖、隱藏不必要細節

測試是在描述需求的情境

1. 從一個全新的需求來探索

怎麼寫個好棒棒的測試

討論好團隊的命名規範

1. 從一個全新的需求來探索

怎麼寫個好棒棒的測試

  1. Arrange – 初始化
  2. Act – 行為,測試對象的執行過程
  3. Assert – 驗證結果

1. 從一個全新的需求來探索

怎麼寫個好棒棒的測試

辨識依賴

 

將其抽取成為方法

 

在測試中覆蓋掉

依賴會影響可測程度,但又不是受測重點

1. 從一個全新的需求來探索

怎麼寫個好棒棒的測試

設計測試案例

一組 if else (分支)

就有正面、反面的表述情境

2/14 情人節快樂?

2/12 情人節快樂?

3/14 情人節快樂?

4/14 情人節快樂?

1. 從一個全新的需求來探索

回想我們的 UI 元件測試

const Card = ({ name, url, content, status }) => {
  const { derivedStatus } = useCardStatus(status);
  return (
    <div>
      <h1>{name}</h1>
      {derivedStatus === "online" ? <SvgOnline /> : <SvgOffline />}
      <img src={url} />
      <p>{content}</p>
      <button onClick={() => openModal(name)}>See more</button>
    </div>
  );
};

我們在意的是什麼?

1. 從一個全新的需求來探索

User 能不能正常、正確地與 Web 互動

我們在意的是什麼?

1. 從一個全新的需求來探索

const Card = ({ name, url, content, status }) => {
  const { derivedStatus } = useCardStatus(status);
  return (
    <div>
      <h1>{name}</h1>
      {derivedStatus === "online" ? <SvgOnline /> : <SvgOffline />}
      <img src={url} />
      <p>{content}</p>
      <button onClick={() => openModal(name)}>See more</button>
    </div>
  );
};

怎麼界定最小的受測單位?

要測什麼?

1. 從一個全新的需求來探索

這個 hook 比較偏向為了Card 封裝隱藏了的實作細節

測 hook?

有可能淪為『過度指定』,測了使用者不需知道的細節

風險:可能重構功能後正確,但測試卻壞了

重構 hook 時,測試必須修改,比如 derivedStatus: "online" | "offline" 改名為 isOnline: boolean

1. 從一個全新的需求來探索

測 <Card />?

import { render } from "@testing-library/react";
const givenStatus = (status) => ({
  name: "Ken",
  content: "Not important",
  url: "",
  status,
});

describe("Card can show different between online and offline", () => {
  it("should render online correctly", () => {
    const { asFragment } = render(<Card {...givenStatus("online")} />);
    expect(asFragment()).toMatchSnapshot();
  });

  it("should renders offline correctly", () => {
    const { asFragment } = render(<Card {...givenStatus("offline")} />);
    expect(asFragment()).toMatchSnapshot();
  });
});

1. 從一個全新的需求來探索

測 <Card />?

測試沒叫,代表在 Card 內的重構是正確的

重構 hook 時,測試不用修改,比如 derivedStatus: "online" | "offline" 改名為 isOnline: boolean

Test Failed 才去檢查重構哪裡有問題

1. 從一個全新的需求來探索

How about <CardList />?

const useGetCardData = (group) => {
  const [data, setData] = useState([]);
  useEffect(() => {
    fetch(`https://some-fake-url/${group}`, { method: "GET" }).then((res) => {
      setData(res.json());
    });
  }, [group]);

  return data;
};

const CardList = (group) => {
  const data = useGetCardData(group);
  return (
    <ul>
      <GoogleMapView /> 
      {data.map((person) => (
        <Card key={person.id} {...person} />
      ))}
    </ul>
  );
};

1. 從一個全新的需求來探索

這個 hook 比較偏向單純要資料

測 hook?

我只要確保資料會長得像 data 這樣,就 Mock 它

專注測試元件行為

測 hook 等於在測 call api, 驗資料

1. 從一個全新的需求來探索

辨識哪些是依賴,Mock 起來

某些ChildComponent -> <GoogleMap /> third party

fetch

some hooks

redux / context

論測試的投資報酬率與覆蓋率

論測試的投資報酬率與覆蓋率

設定一個目標:測試覆蓋率 70%

論測試的投資報酬率與覆蓋率

設定一個目標:測試覆蓋率 70%

今天時間非常緊迫、很短

mainBusiness 非常難測

你會怎麼達成這個目標?

論測試的投資報酬率與覆蓋率

Line 1-70 非常好測,寫完覆蓋率就 70 % 了

Line 71-100 非常難寫,寫完覆蓋率只有 30 % 了

都幾?

論測試的投資報酬率與覆蓋率

如果覆蓋率被作為一種目標

可能導致我們先追求滿足覆蓋率

阿不然我再多寫幾行防呆好了

增加覆蓋率

論測試的投資報酬率與覆蓋率

這段 code 最重要、最有價值的地方是?

哪裡最不允許錯誤

每一行 Code 的價值是不等價的

這 30 行,代表的是整個系統的 80 % 重要性

論測試的投資報酬率與覆蓋率

哪行 code 今天寫了,投資報酬率最高?

不怕未來成為 legacy code 不敢重構,或是有這段測試綠燈能讓你更安心

論測試的投資報酬率與覆蓋率

 > 0%

 評估未覆蓋的地方

 相對的趨勢
V
絕對的數字

 ROI
1. 跟錢有關
2. 很常執行的
3. Bugs
4. 很常改到的

風險、$、商譽

寫測試就像買保險

Recap

軟體的開發 -> 知識的獲取、保存

軟體的品質 -> 1. 軟體架構 2. 測試 3. 組織層面(TBD ?)

如何寫單元測試 ->

1. 辨識受測對象 or 依賴 2. 抽取方法 3. 複寫此方法

如何寫好單元測試 -> 凸顯意圖,隱藏非必要細節

讓測試讀起來像使用者的使用情境

覆蓋率 -> 每行 code 不等價、哪些最有價值?

deck

By 陳Ken

deck

  • 398