美文网首页
Stroybook的基本使用

Stroybook的基本使用

作者: FoxLayla | 来源:发表于2020-03-09 19:52 被阅读0次

    原文:Stroybook的基本使用

    Storybook 作为一个 UI 开发工具,能帮助开发人员独立创建组件,并在隔离的开发环境中以交互方式展示组件。 Storybook 是在主应用程序之外运行的,因此用户可以独立开发 UI 组件,而不必担心应用程序特定的依赖关系和要求。

    Storybook 的基本思想是遵循 CDD + TDD 原则进行组件开发。

    CDD 是 Component Drive Development,即以组件为基础自底而上的开发流程,每个组件都可作为一个独立的 story 进行开发。

    TDD 是 Test Drive Development,在开发过程中对每个组件进行 UI 及功能上的测试。

    本文介绍在 Vue 框架下的 App 中使用 Storybook 的方法。


    以下图所示的 Taskbox App 为例。

    Screen Shot 2020-03-09 at 16.34.56.png

    Setup Vue Storybook

    使用 Vue CLI 创建 App,并启用 Storybook 和 Jest 测试

    # Create our application, using a preset that contains jest:
    npx -p @vue/cli vue create taskbox --preset hichroma/vue-preset-learnstorybook
    
    cd taskbox
    
    # Add Storybook:
    npx -p @storybook/cli sb init
    

    可以使用以下命令检查 App 是否初始化成功:

    # Run the test runner (Jest) in a terminal:
    npm run test:unit
    
    # Start the component explorer on port 6006:
    npm run storybook
    
    # Run the frontend app proper on port 8080:
    npm run serve
    

    创建组件的 Story

    使用 TDD 的开发模式,在开发每个组件时为这个组件创建一个或多个 story,例如:

    • 组件:Task.vue
    • 组件的 story:Task.stories.js

    上述 Taskbox 的最基础组件就是 Task,一个 Task 包含 title、state 两个属性,及 onPinTask、onArchiveTask 两个事件。Task 组件的 story 样例如下:

    // src/components/Task.stories.js
    
    // 使用stroybook提供的addon-actions库来mock事件的处理
    import { action } from '@storybook/addon-actions';
    // 引入组件
    import Task from './Task.vue';
    
    export default {
      // story的标题
      title: 'Task',
      // 排除不需要storybook渲染的文件
      excludeStories: /.*Data$/,
    };
    
    // mock事件处理
    export const actionsData = {
      onPinTask: action('onPinTask'),
      onArchiveTask: action('onArchiveTask'),
    };
    
    // 创建测试数据
    export const taskData = {
      id: '1',
      title: 'Test Task',
      state: 'Task_INBOX'
    };
    
    // Task组件为TASK_PINNED状态时的story
    export const Pinned = () => ({
      components: { Task },
      template: `<task :task="task" @archiveTask="onArchiveTask" @pinTask="onPinTask"/>`,
      props: {
        task: {
          default: () => ({
            ...taskData,
            state: 'TASK_PINNED'
          })
        },
      },
      methods: actionsData,
    });
    
    // Task组件为其他状态的story
    ...
    

    上述 story 样例里包含了几个关键点

    • 通过指定不同 props,我们可以创建不同状态下的组件,以测试组件 UI 是否符合预期
    • 我们只关心组件的行为是否能正确发生(如是否正确传参)而不关心后续的处理,因此我们可以将事件处理 mock 掉
    • 将 mock 的 action、测试数据等 export 出去可以便于我们在其他 story 中复用
    • 每一个 story 都是该组件的一个可视化测试,启动 storybook 后就可以看到对应的测试:
    Screen Shot 2020-03-09 at 17.17.35.png

    运行 story 所需的配置:

    // .storybook/main.js
    module.exports = {
      stories: ["../src/components/**/*.stories.js"],
      addons: ["@storybook/addon-actions", "@storybook/addon-links"]
    };
    

    如果希望 storybook 加载 app 引用的样式文件,还需要配置 preview.js

    // .storybook/preview.js
    import "../src/index.css";
    

    Vuex 对组件 Story 的影响

    当 App 里状态较多时我们会考虑使用 Vuex 管理组件间的数据流,但使用 Vuex 之后组件对外部数据的依赖性增加了,不便于隔离测试, 因此较好的实践是将引入的 Vuex 状态管理抽离成一个容器组件,例如:

    • 原组件:PureTask.vue

      <template>
        <div :class="taskClass">
          <input
            type="checkbox"
            :checked="isChecked"
            @click="$emit('archiveTask', task.id)"
          />
          <input type="text" :readonly="true" :value="this.task.title" />
          <a @click="$emit('pinTask', task.id)"><span class="icon-star"/></a>
        </div>
      </template>
      
      <script>
      export default {
        name: "pure-task",
        props: {
          task: {...}
        },
        ...
      };
      </script>
      
    • 容器组件:Task.vue

      <template>
        <pure-task :task="task" @archiveTask="archiveTask" @pinTask="pinTask" />
      </template>
      
      <script>
      import PureTask from "./PureTask.vue";
      import { mapState, mapActions } from "vuex";
      
      export default {
        name: "task",
        components: { PureTask },
        methods: {
          ...mapActions(["archiveTask", "pinTask"])
        },
        computed: {
          ...mapState(["task"])
        }
      };
      </script>
      

    这样 PureTask 组件就是一个可隔离的独立组件,我们可以对它写 story 测试而不用额外考虑数据上下文。

    此外,当我们用 Task 组件构建外层的 TaskList 组件时,由于自组件 Task 依赖 Vuex 提供的状态 store,父组件 TaskList 不再是一个可隔离的独立组件,需要在它的 story 里提供 Vuex store 以为 Task 组件提供必需的数据上下文。

    //src/components/TaskList.stories.js
    Vue.use(Vuex);
    export const store = new Vuex.Store({
      state: {...},
      actions: {...},
    });
    
    export default {
      title: 'TaskList',
      excludeStories: /.*store$/,
    };
    
    export const Default = () => ({
      components: { TaskList },
      template: `<task-list/>`,
      store,
    });
    

    Snapshot Testing

    快照测试是指记录给定输入的组件的“已知合格”输出,然后在将来输出发生变化时标记该组件的做法。 这样每次测试时可以看到我们对组件的哪部分进行了修改,当单元测试不通过时便于快速定位问题。

    但在使用快照测试时应确保组件呈现不变的数据,以使快照测试不会每次都失败,例如是否存在日期或随机生成的值。

    Setup

    安装依赖

    npm i -D @storybook/addon-storyshots jest-vue-preprocessor
    

    创建 storybook 测试

    // tests/unit/storybook.spec.js
    import initStoryshots from "@storybook/addon-storyshots";
    
    initStoryshots();
    

    在 Jest 配置文件 jest.config.js 中增加以下配置:

    transformIgnorePatterns: ["/node_modules/(?!(@storybook/.*\\.vue$))"]
    

    运行测试

    # 直接运行测试
    npm run test:unit
    
    # 运行测试并更新 snapshot
    npm run test:unit -- -u
    

    Tips

    如果项目中引入了外部组件库,例如 Element-UI,为了处理外部组件库的样式文件,需要把样式文件 mock 掉。可以使用 ES6 Proxy mock CSS Modules:

    npm i -D identity-obj-proxy
    

    并在 Jest 配置文件 jest.config.js 中增加以下配置:

    "moduleNameMapper": {
      "\\.(css|less)$": "identity-obj-proxy"
    }
    

    总结

    Storybook 是一个 UI 开发工具库,它的核心思想是 CDD + TDD。使用 storybook 可以在隔离的开发环境中以交互方式展示独立的组件,方便对组件 UI 和行为进行测试;在团队开发中有助于保证组件 UI 的一致性,也可以避免大家重复造轮子。

    参考

    相关文章

      网友评论

          本文标题:Stroybook的基本使用

          本文链接:https://www.haomeiwen.com/subject/csrmdhtx.html