Browse Source

Docsify Auto Published

Willin Wang 7 years ago
parent
commit
61476e4da0
2 changed files with 193 additions and 0 deletions
  1. 1 0
      _sidebar.md
  2. 192 0
      experience/advanced/coding-as-building.md

+ 1 - 0
_sidebar.md

@@ -91,6 +91,7 @@
     - [Storage](experience/azure/storage.md)
     - [WebAPP](experience/azure/web-app.md)
   - 进阶
+    - [像盖房子一样写代码](experience/advanced/coding-as-building.md)
     - [开源项目翻译正确姿势](experience/advanced/translate.md)
     - [元编程构造优雅解决方案](experience/advanced/meta.md)
     - [Electron桌面应用](experience/advanced/desktop-app.md)

+ 192 - 0
experience/advanced/coding-as-building.md

@@ -0,0 +1,192 @@
+# 像盖房子一样写代码
+
+## 当我写一个功能模块方法时,我在想些什么
+
+```js
+// 无论什么方法,都是这样一个结构
+const fn = () => {
+
+};
+```
+
+比如,我要写一个接口,查询组织下的设备列表 `/api/device/list`
+
+### 地基
+
+```js
+const deviceList = (params) => { // 传入一些参数
+  return []; // 返回一个列表
+};
+```
+
+我需要哪些参数:
+
+- 用户基本信息(主要是用户 id,用户的组织 id)
+- 用户对应的组织基本信息(主要是组织 id,组织管理员 id,层级关系,以及权限逻辑)
+
+输出结果很简单,为一个数组。
+
+### 浇筑
+
+第一步分析,存在成功和错误(错误类型先不考虑)两种类型的结果。
+
+```js
+// 成功
+// 错误
+const deviceList = async (ctx) => {
+  // 错误
+  if(someError) {
+    // 返回错误结果
+  }
+  // 成功
+  return getDevicesByOid(oid);
+};
+```
+
+这是一个大概的设想,没有必要将代码写出来。然后润化该思路,写出第一段框架。
+
+### 主体结构
+
+首先,传入的参数为组织 oid,用户的信息可以通过 session(或其他方式)从内部获得。
+
+#### 可能的一种思路
+
+```js
+// 成功
+// 错误
+// 错误1:用户未加入组织
+// 错误2:传入参数组织不存在
+// 错误3:用户无组织权限
+
+// 传入参数: 要查询的组织 oid
+// 能够通过 session 取到的信息: user
+const deviceList = async (ctx) => {
+  // 用户信息 ctx.user
+  // 判断用户是否有组织
+  if (ctx.user.oid === 0) {
+    // 错误1:用户未加入组织
+  }
+
+  // 如果不传该参数,查询当前用户组织的设备
+  const { oid = ctx.user.oid } = ctx.request.body;
+  if (oid === ctx.user.oid) {
+    // 成功
+    return getDevicesByOid(oid);
+  }
+
+  // 根据oid查询组织信息
+  // 错误2:传入参数组织不存在
+  // 判断是否有权限
+  const checkRights = await checkUserOrgRights(ctx.user.uid, oid);
+  if (!checkRights) {
+    // 错误3:用户无组织权限
+  }
+  // 成功
+  return getDevicesByOid(oid);
+};
+```
+
+#### 推荐的实现方式
+
+```js
+// 成功
+// 错误
+// 错误1:用户未加入组织
+// 错误2:传入参数组织不存在
+// 错误3:用户无组织权限
+
+// 传入参数: 要查询的组织 oid
+// 能够通过 session 取到的信息: user
+const deviceList = async (ctx) => {
+  // 用户信息 ctx.user
+  // 判断用户是否有组织
+  if (ctx.user.oid === 0) {
+    // 错误1:用户未加入组织
+  }
+
+  // 如果不传该参数,查询当前用户组织的设备
+  const { oid = ctx.user.oid } = ctx.request.body;
+  if (oid !== ctx.user.oid) {
+    // 为什么这里不用等于判断:如果等于的话,则当时就需要返回出去,这样的话该方法会有两个成功的 return
+    // 根据oid查询组织信息
+    // 错误2:传入参数组织不存在
+    // 判断是否有权限
+    const checkRights = await checkUserOrgRights(ctx.user.uid, oid);
+    if (!checkRights) {
+      // 错误3:用户无组织权限
+    }
+  }
+  // 成功
+  return getDevicesByOid(oid);
+};
+```
+
+### 封顶
+
+完成其他的业务代码。
+
+## 当我写一段测试的时候,我在想什么
+
+按照上面推荐方式完成代码后,需要进行代码的测试。
+
+首先需要明确业务的流程,理清测试的思路。
+
+- 成功
+- 错误
+  - 错误1:用户未加入组织
+  - 错误2:传入参数组织不存在
+  - 错误3:用户无组织权限
+
+主要有两种设计思路:
+
+### 设计思路
+
+#### 思路一
+
+1. 完成测试用例,覆盖成功的所有情况
+2. 完成测试用例,覆盖错误1的所有情况
+3. 完成测试用例,覆盖错误2的所有情况
+4. 完成测试用例,覆盖错误3的所有情况
+
+这是传统的单元测试衍生而来的 BDD 测试方式。
+
+这里测试用例的个数应该为`8`次:
+
+- 成功:
+  - 1.当前组织的用户有传入组织 oid
+  - 2.当前组织的用户未传入组织 oid
+  - 3-5.上级组织,上上级组织,根级组织的管理员用户传入组织 oid 
+- 6.失败1:用户未加入组织
+- 7.失败2:传入参数组织不存在
+- 8.失败3:用户无组织权限
+
+其中,测试3-5可以优化为一次测试(即根据所有管理员 uid 的数组比较是否包含当前用户 uid),最终优化后的结果应当为`6`次。
+
+但由于该思路中不明确用户,所以用户行为无法准确表达,在创建测试数据的时候较为困难,不仔细思考分析,无法优化需要创建多少条测试数据。
+
+#### 思路二
+
+而实际上 BDD 测试为用户行为测试,可以以几类用户的情形分别进行测试。
+
+1. 模拟一个用户的数据,覆盖成功和可能错误(有可能无法涵盖到所有错误)的所有情况
+2. 根据未覆盖的部分,再模拟另一个用户的数据,覆盖成功和可能错误(有可能无法涵盖到所有错误)的所有情况
+
+以此循环,直至覆盖所有。
+
+- 用户1(非组织管理员,查询自己的组织)
+  - 1.成功(未传入组织 oid)
+  - 2.成功(传入组织 oid)
+  - 3.失败2:传入参数组织不存在
+  - 4.失败3:用户无组织权限
+- 用户2(上级某组织管理员)
+  - 5.成功
+- 用户3(未加入组织用户用户)
+  - 6.失败1:用户未加入组织
+
+非常简洁明了的关系,需要3个测试用户,2个组织(上下级关系进行数据复用),即可涵盖所有范围。
+
+## 当我以测试驱动开发的时候,我在想些什么
+
+可以从上述测试思路二中进行反推。
+
+