diff --git a/.gitignore b/.gitignore
index 19514b8..c5cfd2b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,5 +15,7 @@ npm-debug.log
 *.swp
 *.swo
 *.log
-test/dist/
+examples/dist/
 dist/
+yarn-error.log
+test/unit/coverage
\ No newline at end of file
diff --git a/build/webpack.base.config.js b/build/webpack.base.config.js
index 80fe22c..2a258de 100644
--- a/build/webpack.base.config.js
+++ b/build/webpack.base.config.js
@@ -2,6 +2,10 @@
  * 公共配置
  */
 var webpack = require('webpack');
+var path = require('path');
+function resolve (dir) {
+  return path.join(__dirname, '..', dir)
+}
 
 module.exports = {
     // 加载器
@@ -55,6 +59,10 @@ module.exports = {
         ]
     },
     resolve: {
-        extensions: ['.js', '.vue']
+        extensions: ['.js', '.vue'],
+        alias: {
+          'vue': 'vue/dist/vue.esm.js',
+          '@': resolve('src'),
+        }
     }
 };
diff --git a/build/webpack.dev.config.js b/build/webpack.dev.config.js
index a587fee..699d29e 100644
--- a/build/webpack.dev.config.js
+++ b/build/webpack.dev.config.js
@@ -8,6 +8,7 @@ var webpack = require('webpack');
 var HtmlWebpackPlugin = require('html-webpack-plugin');
 var merge = require('webpack-merge')
 var webpackBaseConfig = require('./webpack.base.config.js');
+var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
 
 
 module.exports = merge(webpackBaseConfig, {
@@ -35,6 +36,7 @@ module.exports = merge(webpackBaseConfig, {
             inject: true,
             filename: path.join(__dirname, '../examples/dist/index.html'),
             template: path.join(__dirname, '../examples/index.html')
-        })
+        }),
+        new FriendlyErrorsPlugin()
     ]
 });
diff --git a/build/webpack.test.config.js b/build/webpack.test.config.js
new file mode 100644
index 0000000..6c3212b
--- /dev/null
+++ b/build/webpack.test.config.js
@@ -0,0 +1,25 @@
+/**
+ * 用于单元测试
+ */
+
+var webpack = require('webpack')
+var merge = require('webpack-merge')
+var webpackBaseConfig = require('./webpack.base.config.js');
+
+
+var webpackConfig = merge(webpackBaseConfig, {
+  // use inline sourcemap for karma-sourcemap-loader
+  devtool: '#inline-source-map',
+  plugins: [
+    new webpack.DefinePlugin({
+      'process.env': {
+        NODE_ENV: '"testing"'
+      }
+    })
+  ]
+})
+
+// no need for app entry during tests
+delete webpackConfig.entry
+
+module.exports = webpackConfig
diff --git a/package.json b/package.json
index 40a5423..7a89f0e 100644
--- a/package.json
+++ b/package.json
@@ -25,7 +25,8 @@
     "dist:prod": "webpack --config build/webpack.dist.prod.config.js",
     "dist": "npm run dist:style && npm run dist:dev && npm run dist:prod",
     "lint": "eslint --fix --ext .js,.vue src",
-    "test": "npm run dist && npm run lint",
+    "unit": "cross-env BABEL_ENV=test karma start test/unit/karma.conf.js --single-run",
+    "test": "npm run unit",
     "prepublish": "npm run dist"
   },
   "repository": {
@@ -54,11 +55,14 @@
     "babel-plugin-transform-runtime": "^6.12.0",
     "babel-preset-es2015": "^6.9.0",
     "babel-runtime": "^6.11.6",
+    "chai": "^3.5.0",
+    "cross-env": "^3.1.4",
     "css-loader": "^0.23.1",
     "eslint": "^3.12.2",
     "eslint-plugin-html": "^1.7.0",
     "extract-text-webpack-plugin": "^2.0.0",
     "file-loader": "^0.8.5",
+    "friendly-errors-webpack-plugin": "^1.6.1",
     "gulp": "^3.9.1",
     "gulp-autoprefixer": "^3.1.1",
     "gulp-clean-css": "^2.0.13",
@@ -66,8 +70,21 @@
     "gulp-rename": "^1.2.2",
     "html-loader": "^0.3.0",
     "html-webpack-plugin": "^2.28.0",
+    "karma": "^1.4.1",
+    "karma-coverage": "^1.1.1",
+    "karma-mocha": "^1.3.0",
+    "karma-phantomjs-launcher": "^1.0.2",
+    "karma-sinon-chai": "^1.2.4",
+    "karma-sourcemap-loader": "^0.3.7",
+    "karma-spec-reporter": "0.0.26",
+    "karma-webpack": "^2.0.2",
     "less": "^2.7.1",
     "less-loader": "^2.2.3",
+    "lolex": "^1.5.2",
+    "mocha": "^3.2.0",
+    "phantomjs-prebuilt": "^2.1.13",
+    "sinon": "^1.17.7",
+    "sinon-chai": "^2.8.0",
     "style-loader": "^0.13.1",
     "url-loader": "^0.5.7",
     "vue": "^2.2.1",
@@ -80,5 +97,9 @@
     "webpack": "^2.2.1",
     "webpack-dev-server": "^2.4.1",
     "webpack-merge": "^3.0.0"
+  },
+  "engines": {
+    "node": ">= 4.0.0",
+    "npm": ">= 3.0.0"
   }
 }
diff --git a/test b/test
deleted file mode 100644
index e69de29..0000000
--- a/test
+++ /dev/null
diff --git a/test/unit/index.js b/test/unit/index.js
new file mode 100644
index 0000000..b282cc3
--- /dev/null
+++ b/test/unit/index.js
@@ -0,0 +1,18 @@
+import Vue from 'vue';
+Vue.config.productionTip = false;
+
+// Polyfill fn.bind() for PhantomJS
+/* eslint-disable no-extend-native */
+Function.prototype.bind = require('function-bind');
+
+// require all test files (files that ends with .spec.js)
+const testsContext = require.context('./specs', true, /\.spec$/);
+testsContext.keys().forEach(testsContext);
+
+// require all src files except main.js for coverage.
+// you can also change this to match only the subset of files that
+// you want coverage for.
+// const srcContext = require.context('../../src', true, /^\.\/(?!main(\.js)?$)/);
+// @todo
+const srcContext = require.context('../../src/components/breadcrumb', true, /^\.\/(?!styles.*?(\.less)?$)/);
+srcContext.keys().forEach(srcContext);
diff --git a/test/unit/karma.conf.js b/test/unit/karma.conf.js
new file mode 100644
index 0000000..2fe05ea
--- /dev/null
+++ b/test/unit/karma.conf.js
@@ -0,0 +1,33 @@
+// This is a karma config file. For more details see
+//   http://karma-runner.github.io/0.13/config/configuration-file.html
+// we are also using it with karma-webpack
+//   https://github.com/webpack/karma-webpack
+
+var webpackConfig = require('../../build/webpack.test.config.js');
+
+module.exports = function (config) {
+  config.set({
+    // to run in additional browsers:
+    // 1. install corresponding karma launcher
+    //    http://karma-runner.github.io/0.13/config/browsers.html
+    // 2. add it to the `browsers` array below.
+    browsers: ['PhantomJS'],
+    frameworks: ['mocha', 'sinon-chai'],
+    reporters: ['spec', 'coverage'],
+    files: ['./index.js'],
+    preprocessors: {
+      './index.js': ['webpack', 'sourcemap']
+    },
+    webpack: webpackConfig,
+    webpackMiddleware: {
+      noInfo: true,
+    },
+    coverageReporter: {
+      dir: './coverage',
+      reporters: [
+        { type: 'lcov', subdir: '.' },
+        { type: 'text-summary' },
+      ]
+    },
+  });
+};
diff --git a/test/unit/specs/breadcrumb.spec.js b/test/unit/specs/breadcrumb.spec.js
new file mode 100644
index 0000000..3166c41
--- /dev/null
+++ b/test/unit/specs/breadcrumb.spec.js
@@ -0,0 +1,24 @@
+import { createVue, destroyVM } from '../util';
+
+describe('Breadcrumb.vue', () => {
+  let vm;
+  afterEach(() => {
+    destroyVM(vm);
+  });
+  it('create', done => {
+    vm = createVue(`
+      <Breadcrumb separator="<b class='demo-breadcrumb-separator'>=></b>">
+        <Breadcrumb-item href="/">Home4</Breadcrumb-item>
+        <Breadcrumb-item href="/components/breadcrumb">Components</Breadcrumb-item>
+        <Breadcrumb-item>Breadcrumb</Breadcrumb-item>
+      </Breadcrumb>
+    `);
+    expect(vm.$el.querySelectorAll('.ivu-breadcrumb-item-link').length).to.equal(3);
+
+    vm.$nextTick(_ => {
+      // console.log(vm.$el.querySelector('.ivu-breadcrumb-item-separator').innerHTML);
+      expect(vm.$el.querySelector('.ivu-breadcrumb-item-separator').innerHTML).to.equal('<b class="demo-breadcrumb-separator">=&gt;</b>');
+      done();
+    });
+  });
+});
\ No newline at end of file
diff --git a/test/unit/util.js b/test/unit/util.js
new file mode 100644
index 0000000..47cd331
--- /dev/null
+++ b/test/unit/util.js
@@ -0,0 +1,85 @@
+import Vue from 'vue';
+import iView from '../../src/index';
+
+Vue.use(iView);
+
+let id = 0;
+
+const createElm = function() {
+  const elm = document.createElement('div');
+
+  elm.id = 'app' + ++id;
+  document.body.appendChild(elm);
+
+  return elm;
+};
+
+/**
+ * 回收 vm
+ * @param  {Object} vm
+ */
+exports.destroyVM = function(vm) {
+  vm.$el &&
+  vm.$el.parentNode &&
+  vm.$el.parentNode.removeChild(vm.$el);
+};
+
+/**
+ * 创建一个 Vue 的实例对象
+ * @param  {Object|String}  Compo   组件配置,可直接传 template
+ * @param  {Boolean=false} mounted 是否添加到 DOM 上
+ * @return {Object} vm
+ */
+exports.createVue = function(Compo, mounted = false) {
+  const elm = createElm();
+
+  if (Object.prototype.toString.call(Compo) === '[object String]') {
+    Compo = { template: Compo };
+  }
+  return new Vue(Compo).$mount(mounted === false ? null : elm);
+};
+
+/**
+ * 创建一个测试组件实例
+ * @link http://vuejs.org/guide/unit-testing.html#Writing-Testable-Components
+ * @param  {Object}  Compo          - 组件对象
+ * @param  {Object}  propsData      - props 数据
+ * @param  {Boolean=false} mounted  - 是否添加到 DOM 上
+ * @return {Object} vm
+ */
+exports.createTest = function(Compo, propsData = {}, mounted = false) {
+  if (propsData === true || propsData === false) {
+    mounted = propsData;
+    propsData = {};
+  }
+  const elm = createElm();
+  const Ctor = Vue.extend(Compo);
+  return new Ctor({ propsData }).$mount(mounted === false ? null : elm);
+};
+
+/**
+ * 触发一个事件
+ * mouseenter, mouseleave, mouseover, keyup, change, click 等
+ * @param  {Element} elm
+ * @param  {String} name
+ * @param  {*} opts
+ */
+exports.triggerEvent = function(elm, name, ...opts) {
+  let eventName;
+
+  if (/^mouse|click/.test(name)) {
+    eventName = 'MouseEvents';
+  } else if (/^key/.test(name)) {
+    eventName = 'KeyboardEvent';
+  } else {
+    eventName = 'HTMLEvents';
+  }
+  const evt = document.createEvent(eventName);
+
+  evt.initEvent(name, ...opts);
+  elm.dispatchEvent
+    ? elm.dispatchEvent(evt)
+    : elm.fireEvent('on' + name, evt);
+
+  return elm;
+};
--
libgit2 0.21.4