# 任务 1：任务分解 - 驱动组件树拆分

Hey，你好，我是 JimmyLv 吕靖，这是你开始 前端 TDD 练习的第 19 天，这是 ShoppingCart 项目的第一天，这也是你的最后一个练习项目，时间过得很快不是吗？还记得你在前言中 [WOOP](https://jimmylv.gitbook.io/tdd-frontend/02-course) 时的**愿望(Wish)**&#x5417;？

![WOOP 卡片](https://2897586075-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LquCgGNObzQ8HY5Og1P%2F-M0Doa0ZBxLtz0KDOt4Y%2F-M0DoayqdPAHNBDB5Oez%2FWOOP.jpg?generation=1581870387892439\&alt=media)

ShoppingCart 的复杂度其实并不算高，但是更像你日常能遇到的真实需求。产品的想法描述得并不清晰，甚至前后矛盾，这就需要你做好需求确认、范围框定、优先级排序、任务拆解的动作。

另外，这个问题需要做哪些异常处理，需求也没有清晰描述，很可能你会在开发过程中突然想起某个异常情况需要处理。这时就需要把握好测试驱动开发的节奏，不要在没有测试的情况下贸然添加新的逻辑。

## 练习目标

* [ ] 使用 E2E 测试驱动组件树拆分，即任务分解
* [ ] 完成第一部分业务：C 端商品购买的静态部分

```bash
git clone https://github.com/JimmyLv/tdd-shoppingcart.git
cd tdd-shoppingcart

yarn install #安装依赖
yarn start #启动应用
```

当然，我还通过 CodeSandbox 的方式作为你的快速入口：<https://codesandbox.io/s/github/JimmyLv/tdd-shoppingcart> (可能需要科学上网)，帮助你去除设置开发环境的障碍，一键打开 Cloud IDE 即可开始编码，右边则是你的网站运行效果预览，比如：<https://tdd-shoppingcart.jimmylv.now.sh/>

{% embed url="<https://codesandbox.io/embed/github/JimmyLv/tdd-shoppingcart/tree/master/?autoresize=1&fontsize=14&hidenavigation=1&theme=dark>" %}
CodeSandbox 代码编辑
{% endembed %}

[![Edit tdd-shoppingcart](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/github/JimmyLv/tdd-shoppingcart/tree/master/?fontsize=14\&hidenavigation=1\&theme=dark)

在前面的项目当中，你已经知道了在 React 的世界里，页面就是由很多的组件构成的，所以我们要做的第一步就是进行组件拆分，即任务分解。

与此同时，你也掌握了前端测试手段，如果还没有的话可以回去先预习一下，涵盖所有维度的测试及其应用测试策略，先给你一个思考题：在什么时机引入相对应的测试是最合适的？

![测试金字塔](https://2897586075-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LquCgGNObzQ8HY5Og1P%2F-LqvN8fjJNwcmDOlf1kK%2F-LqvN9X8AxowtIzD8_7z%2Fideal-test-pyramid.png?generation=1570806765617764\&alt=media)

1. 页面级别：用户 E2E 行为 Cypress 搞定
2. 组件级别：交互逻辑 jest+testing-library 搞定
3. 组件级别：UI 样式 story+snapshot 搞定
4. 数据流 model：redux-saga-test-plan 搞定
5. selectors， 最简单的纯 JavaScript 逻辑单测
6. services，直接 mock 数据返回，API 请求则留给契约测试
7. utils，也是最简单的纯 JavaScript 单元测试
8. API 契约测试，Pact 接口验证与 Mock 数据返回

我的答案非常简单：在需要的时候。我想说的是，你先不用害怕有这么多种测试方法，而是要彻底转换思维：“我在写代码的时候，需要的是什么？”

> 我们不是在头脑里创造，我们通过用素描、涂鸦和草图**外化**我们的思想来创造，以便我们能够从现实的不可预测中获益。正是这种持续变化的不可预测才使得与真实世界的交互变得如此有趣。 —— [《英 Chris Frith - 心智的构建：脑如何创造我们的精神世界》](https://book.douban.com/subject/10864488/)

而任务分解，测试先行，就是我们在编程创造时的实例化想象中的需求、外化思想的一种手段。

## 本次任务的练习要求

* 扮演真实用户，从用户角度来使用你的产品，有哪些购买路径？
* 编写 E2E 测试，先写一个完整购买路径的测试用例，再去实现代码。
* 由外至内，再去拆分组件树，用静态 HTML 先把一个个组件提取出来。
* 把所有组件都新建出来之后，这便是你实战中的任务分解，动起手来！

```javascript
describe('ShoppingCart', () => {
  beforeEach(() => {
    cy.visit('/')
  })
  it('user checkout products and see product count and total price', () => {
    // should show cart title
    cy.contains('React Shopping Cart')

    // should add new product item into cart
    addNewProduct('ITEM001')
    cy.queryAllByTestId('productCode').should('contain', 'ITEM001')

    // should calculate total price given product list
    addNewProduct('ITEM002')
    cy.queryByTestId('totalPrice').should('have.text', '￥200')

    // should only increase product count when add same product code
    addNewProduct('ITEM002')
    addNewProduct('ITEM002')
    getFieldByTestId('ITEM002', 'productCount').should('have.text', '2')

    // should increase and decrease product count when click counter
    getFieldByTestId('ITEM001', 'increaseCount').click()
    getFieldByTestId('ITEM001', 'productCount').should('have.text', '2')
    getFieldByTestId('ITEM001', 'decreaseCount').click()
    getFieldByTestId('ITEM001', 'productCount').should('have.text', '1')
  })

  function getFieldByTestId(productCode, testId) {
    return cy.contains(productCode).siblings().children(`[data-testid="${testId}"]`)
  }

  function addNewProduct(productCode) {
    cy.queryByTestId('newProduct').click()
    cy.queryByTestId('newProductCode').type(productCode)
    cy.queryByTestId('addProduct').click()
  }
})
```

最终你可能将产出这样的一颗组件树：

```bash
tree src/components

src/components
├── AddProduct.js
├── Counter.js
├── Modal.js
├── MiniCart.js
├── ProductList.js
└── ShoppingCart.js
```

然后呢，此时所有组件都还是静态的，没有样式的，别着急，后边儿我们就会由外到内，进入单个组件的测试驱动开发了，还记得 Testing Library 吗？它会帮助你测试单个组件的行为，你将会逐一通过测试驱动开发所有的组件行为，样式则会由 Storybook 覆盖，最终使得整个 E2E 测试通过。

![TDD\_Global\_Lifecycle](https://2897586075-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LquCgGNObzQ8HY5Og1P%2F-M4KE3VpsTiUdO_PSQIB%2F-M4KE4D2jmydk9tplElc%2FTDD_Global_Lifecycle.png?generation=1586272950130212\&alt=media)

## 也请你思考一下

我相信你已经或多或少体会到了为什么我们要写测试，以及测试在整个知识消费编程活动中所扮演的角色，它是如何减少了我们的认知负担，让编程创造变成一件如此有趣的事情的呢？

### 贝叶斯脑如何构造世界的模型？

![Bayes' theorem](https://2897586075-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LquCgGNObzQ8HY5Og1P%2F-M4gcngSdLf-vAmHE110%2F-M4gcoYjSbprvNKpOZv3%2FBayes'%20theorem.png?generation=1586665573681930\&alt=media)

> 感知是一种与现实相符的幻觉 —— [《英 Chris Frith - 心智的构建：脑如何创造我们的精神世界》](https://book.douban.com/subject/10864488/)

从认知科学来说，我的大脑可以根据它对世界的认知信念来预测活动的模式，这些活动模式是通过我的眼睛、耳朵和其他感官来察觉的：`p(X\A)`。如果在预测时出现误差， 会发生什么呢？这些误差十分重要，因为我的脑能利用它们来更新脑对世界的认知信念， 并产生出一个更好的认知信念：`p(A\X)`。一旦这种更新发生，我的脑对世界就产生了一个新的认知信念，并对通过我的感官察觉的活动模式进行新的预测。

我的脑会重复这个过程，每循环一次，预测误差就变小一次。当**预测误差**变得足够小时，我的脑就可以“知道”外在世界那边的东西是何物了。这一切发生得如此之快，以至于我根本意识不到这个复杂过程。知晓外在世界那边的东西是何物对于我来说好像是件轻而易举的事情，但我的脑陷人这无止境的预测和更新的循环中，一刻不得停歇。

![双关图像](https://2897586075-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LquCgGNObzQ8HY5Og1P%2F-M4gcngSdLf-vAmHE110%2F-M4gcoYlkPbzN_K4Ll8f%2F%E5%8F%8C%E5%85%B3%E5%9B%BE%E5%83%8F.png?generation=1586665574135819\&alt=media)

甚至像鲁宾花瓶（Rubin vase）图和妻子/岳母图这样的更为复杂的图形，同样可显示知觉从一个知觉对象到另一个知觉对象的自然转换，还是因为这两种视图都看似合理的。我们的脑对双关图形作出这种反应的事实进一步证明了我们的脑是一个**贝叶斯机器**，这种贝叶斯机器通过作出预测和搜寻知觉起因来发现世界中的物体。
