Pair programming is an agile software development technique in which two programmers work together at one workstation. One, the driver, writes code while the other, the observer or navigator,[1]reviews each line of code as it is typed in. The two programmers switch roles frequently.
While reviewing, the observer also considers the “strategic” direction of the work, coming up with ideas for improvements and likely future problems to address. This is intended to free the driver to focus all of their attention on the “tactical” aspects of completing the current task, using the observer as a safety net and guide.
Jenkins initial setup is required. An admin user has been created and a password generated. Please use the following password to proceed to installation:
63196690ae7d47c49506480ee0e1af4a
This may also be found at: /root/.jenkins/secrets/initialAdminPassword
echo'The following command terminates the "npm run serve" process using its PID' echo'(written to ".pidfile"), all of which were conducted when "deloy.sh"' echo'was executed.' set -x kill $(cat .pidfile)
思路:
使用一台单独的Nginx服务器发布,使用Publish Over SSH插件上传
使用Docker在本地发布或者远程发布
使用Dockerfile进行镜像内的构建,使用docker镜像进行发布
TravisCI
使用简介
Travis CI 只支持 Github,不支持其他代码托管服务。这意味着,你必须满足以下条件,才能使用 Travis CI。
在settings->Developer settings->Personal access tokens->Generate new token
配置项目的.travis.yml文件
设置项目语言
1 2 3
language:node_js node_js: -"10"
Script脚本
Travis 的运行流程很简单,任何项目都会经过两个阶段。
install 阶段:安装依赖
script 阶段:运行脚本
配置文件:
1 2 3 4 5 6 7 8 9 10
# S: Build Lifecycle install: -yarninstall
//钩子方法 before_script:
# 无其他依赖项所以执行npm run build 构建就行了 script: -npmrunbuild
如果不需要安装,即跳过安装阶段,就直接设为true。
1
install: true
script字段用来指定构建或测试脚本。
1
script: bundle exec thor build
如果有多个脚本,可以写成下面的形式。
1 2 3
script: - command1 - command2
部署到github pages
1 2 3 4 5 6 7 8 9
deploy: # 其他的一些配置项,可以参考:https://docs.travis-ci.com/user/deployment/pages/ provider:pages skip_cleanup:true local_dir:dist/ github_token:$GITHUB_TOKEN# Set in the settings page of your repository, as a secure variable keep_history:false on: branch:master
# 无其他依赖项所以执行npm run build 构建就行了 script: -npmrunbuild
deploy: # 其他的一些配置项,可以参考:https://docs.travis-ci.com/user/deployment/pages/ provider:pages skip_cleanup:true local_dir:dist/ github_token:$GITHUB_TOKEN# Set in the settings page of your repository, as a secure variable keep_history:false on: branch:master
#!/bin/sh # ideas used from https://gist.github.com/motemen/8595451
# Based on https://github.com/eldarlabs/ghpages-deploy-script/blob/master/scripts/deploy-ghpages.sh # Used with their MIT license https://github.com/eldarlabs/ghpages-deploy-script/blob/master/LICENSE
# abort the script if there is a non-zero error set -e
# show where we are on the machine pwd remote=$(git config remote.origin.url)
echo'remote is: '$remote
# make a directory to put the gp-pages branch mkdir gh-pages-branch cd gh-pages-branch # now lets setup a new repo so we can update the gh-pages branch git config --global user.email "$GH_EMAIL" > /dev/null 2>&1 git config --global user.name "$GH_NAME" > /dev/null 2>&1 git init git remote add --fetch origin "$remote"
echo'email is: '$GH_EMAIL echo'name is: '$GH_NAME echo'sitesource is: '$siteSource
# switch into the the gh-pages branch if git rev-parse --verify origin/gh-pages > /dev/null 2>&1 then git checkout gh-pages # delete any old site as we are going to replace it # Note: this explodes if there aren't any, so moving it here for now git rm -rf . else git checkout --orphan gh-pages fi
# copy over or recompile the new site cp -a "../${siteSource}/." .
ls -la
# stage any changes and new files git add -A # now commit, ignoring branch gh-pages doesn't seem to work, so trying skip git commit --allow-empty -m "Deploy to GitHub pages [ci skip]" # and push, but send any output to /dev/null to hide anything sensitive git push --force --quiet origin gh-pages # go back to where we started and remove the gh-pages git repo we made and used # for deployment cd .. rm -rf gh-pages-branch
在多浏览器的自动化测试,我们多半是进行端到端的测试工作,一小部分是大粒度的单元测试。端到端测试测试模拟用户的行为。在 Web 应用程序中,他们会启动服务器,打开浏览器,模拟用户的行为进行点击、输入、提交等动作,断言浏览器中发生了特定的事情或者是得到了期待的结果,从而让我们相信功能可以正常的运行。
而单元测试根据代码单元的公共 API 运行它们。这些测试需要创建一个类的实例,使用特定的输入调用它的方法,断言被调用的方法达到了预期的效果。在下文中我们会看到这两种测试的实践,当然有时候区分度并不大,可能无法明显地区分哪些是端对端测试哪些是单元测试,有时候他们是混合起来的,不过只要记住我们的目标是保证功能可以正常运行救足够了。
A test runner is the library or tool that picks up an assembly (or a source code directory) that contains unit tests, and a bunch of settings, and then executes them and writes the test results to the console or log files. there are many runners for different languages. See Nunit and MSTest for C#, or Junit for Java.
karma 设计目标主要有下面四点:
高效 扩展性 运行在真实设备 无缝的使用流程
karma 是一个典型的 C/S 程序,包含 client 和 server ,通讯方式基于 Http ,通常情况下,客户端和服务端基本都运行在开发者本地机器上。
describe("a suite of tests", function () { beforeEach(function (done) { this.timeout(3000); // A very long environment setup. setTimeout(done, 2500); }); });
var expect = require("chai").expect; describe("db", function () { it("#get", function (done) { db.get("foo", function (err, foo) { if (err) done(err); expect(foo).to.equal("bar"); done(); }); }); });
快速互动式监控模式可以监控到哪些测试文件有过改动,只运行与改动过的文件相关的测试,并且由于优化作用,能迅速放出监控信号。设置起来非常简单,而且还有一些别的选项,可以用文件名或测试名来过滤测试。我们用 Mocha 时也有监控模式,不过没有那么强大,要运行某个特定的测试文件夹或文件,就不得不自己创造解决方法,而这些功能 Jest 本身就已经提供了,不用花力气。
代码覆盖率&测试报告
Jest 内置有代码覆盖率报告功能,设置起来易如反掌。可以在整个项目范围里收集代码覆盖率信息,包括未经受测试的文件。
要使完善 Circle CI 整合,只需要一个自定义报告功能。有了 Jest,用jest-junit-reporter就可以做到,其用法和 Mocha 几乎相同。
describe("getNum", () => { it("should select numbber based on index if provided", () => { expect(getNum(1)).toBe(2); });
it("should select a random number based on Math.random if skuId not available", () => { const spy = jest.spyOn(Math, "random").mockImplementation(() =>0.9);
➜ npx jest --help Usage: jest [--config=<pathToConfigFile>] [TestPathPattern]
选项: --help, -h 显示帮助信息 [布尔] --version, -v Print the version and exit [布尔] --config, -c The path to a jest config file specifying how to find and execute tests. Ifno rootDir issetin the config, the directory containing the config fileis assumed to be the rootDir for the project.This can also be a JSON encoded value which Jest will useas configuration. [字符串] --coverage Indicates that test coverage information should be collected and reported in the output. [布尔] --timers Setting this value to fake allows the use of fake timers for functions such as setTimeout. [字符串] --verbose Display individual test results with the test suite hierarchy. [布尔] --watch Watch files for changes and rerun tests related tochanged files. If you want to re-run all tests when a file has changed, use the `--watchAll` option. [布尔] --watchAll Watch files for changes and rerun all tests. If you want to re-run only the tests related to the changed files, use the `--watch` option. [布尔] ...
➜ npx jest --verbose PASS test/mock.test.js bot ✓ should say hello (7ms)
console.log test/mock.test.js:10 Hello mix Michael
PASS test/domain.test.js getImageDomain ✓ should select domain based on skuId if provided (1ms) ✓ should select a random domain based on Math.random if skuId not available (1ms)
PASS test/sayhello.test.js bot ✓ should say hello (6ms)
PASS test/num.test.js getNum ✓ should select numbber based on index if provided (1ms) ✓ should select a random number based on Math.random if skuId not available
PASS test/math.test.js math ✓ #should return result as a+b (1ms) ✓ #should return result as a*b (4ms)
Test Suites: 5 passed, 5 total Tests: 8 passed, 8 total Snapshots: 0 total Time: 1.075s Ran all test suites.
--watch和--watchAll用来监听测试文件的变化
1 2 3 4 5 6 7 8 9
Ran all test suites.
Watch Usage › Press f to run only failed tests. › Press o to only run tests related to changed files. › Press p to filter by a filename regex pattern. › Press t to filter by a test name regex pattern. › Press q to quit watch mode. › Press Enter to trigger a test run.
Enzyme is a JavaScript Testing utility for React that makes it easier to test your React Components’ output. You can also manipulate, traverse, and in some ways simulate runtime given the output.
Enzyme’s API is meant to be intuitive and flexible by mimicking jQuery’s API for DOM manipulation and traversal
Options --watch, -w Re-run tests when tests and source files change --match, -m Only run tests with matching title (Can be repeated) --update-snapshots, -u Update snapshots --fail-fast Stop after first test failure --timeout, -T Set global timeout (milliseconds or human-readable, e.g. 10s, 2m) --serial, -s Run tests serially --concurrency, -c Max number of test files running at the same time (Default: CPU cores) --verbose, -v Enable verbose output --tap, -t Generate TAP output --color Force color output --no-color Disable color output --reset-cache Reset AVA's compilation cache and exit --config JavaScript file for AVA to read its config from, instead of using package.json or ava.config.js files Examples ava ava test.js test2.js ava test-*.js ava test The above relies on your shell expanding the glob patterns. Without arguments, AVA uses the following patterns: **/test.js **/test-*.js **/*.spec.js **/*.test.js **/test/**/*.js **/tests/**/*.js **/__tests__/**/*.js
文件匹配
使用match指令,匹配对应需要测试的文件:
匹配标题以foo:结尾
1
npx ava --match ='* foo'
匹配标题以foo:
1
npx ava --match ='foo *'
匹配标题包含foo:
1
npx ava --match ='* foo *'
匹配是完全相同 foo:
1
npx ava --match ='foo'
匹配标题不包含foo:
1
npx ava --match ='!* foo *'
匹配以下foo结尾的标题bar:
1
npx ava --match ='foo * bar'
匹配foo以bar:开头或结尾的标题:
1
npx ava --match ='foo *' - match ='* bar'
关于 reporter
默认情况下,AVA 使用最小的报告:
使用该--verbose标志启用详细的报告者。除非启用 TAP 报告,否则始终在 CI 环境中使用此选项。
TAP 报告(推荐)
AVA 支持 TAP 格式,因此与任何 TAP 报告器兼容。使用该--tap标志启用 TAP 输出。
~/Downloads/Demo is 📦 v1.0.0 via ⬢ v10.16.0 ➜ npx karma init
# 如果在应用中用到了其它的测试框架,那就需要我们安装它们所对应的插件,并在配置文件中标注它们(详见 karma.conf.js 中的 plugins 项) Which testing framework do you want to use ? Press tab to list possible options. Enter to move to the next question. > jasmine # mocha # qunit # nodeunit # nunit
# Require.js 是异步加载规范(AMD)的实现。常被作为基础代码库,应用在了很多的项目与框架之中,例如 Dojo, AngularJs 等 Do you want to use Require.js ? This will add Require.js plugin. Press tab to list possible options. Enter to move to the next question. > no # yes
# 选择需要运行测试用例的浏览器。需要注意的就是,必须保证所对应的浏览器插件已经安装成功。 Do you want to capture any browsers automatically ? Press tab to list possible options. Enter empty string to move to the next question. > Chrome # ChromeHeadless # ChromeCanary # Firefox # Safari # PhantomJS # Opera # IE
# 选择测试用例所在的目录位置。Karma 支持通配符的方式配置文件或目录,例如 *.js, test/**/*.js 等。如果目录或文件使用相对位置,要清楚地是,此时的路径是相对于当前运行 karma 命令时所在的目录。 What is the location of your source and test files ? You can use glob patterns, eg. "js/*.js" or "test/**/*Spec.js". Enter empty string to move to the next question. > src/*js
# 目录中不包括的那些文件。 Should any of the files included by the previous patterns be excluded ? You can use glob patterns, eg. "**/*.swp". Enter empty string to move to the next question.
# 是否需要 Karma 自动监听文件?并且文件一旦被修改,就重新运行测试用例? Do you want Karma to watch all the files and run the tests on change ? Press tab to list possible options. > yes
module.exports = function (config) { config.set({ // base path that will be used to resolve all patterns (eg. files, exclude) basePath: "",
// frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter frameworks: ["mocha"],
// list of files / patterns to load in the browser files: ["src/*js"],
// list of files / patterns to exclude exclude: [],
// preprocess matching files before serving them to the browser // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: {},
// test results reporter to use // possible values: 'dots', 'progress' // available reporters: https://npmjs.org/browse/keyword/karma-reporter reporters: ["progress"],
// web server port port: 9876,
// enable / disable colors in the output (reporters and logs) colors: true,
// level of logging // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes autoWatch: true,
// start these browsers // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher browsers: ["Chrome"],
// Continuous Integration mode // if true, Karma captures browsers, runs the tests and exits singleRun: false,
// Concurrency level // how many browser should be started simultaneous concurrency: Infinity, }); };
babel and karma-babel-preprocessor only convert ES6 modules to CommonJS/AMD/SystemJS/UMD. If you choose CommonJS, you still need to resolve and concatenate CommonJS modules on your own. We recommend karma-browserify + babelify or webpack + babel-loader in such cases.
module.exports = function (config) { config.set({ // base path that will be used to resolve all patterns (eg. files, exclude) basePath: "",
// frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter frameworks: ["mocha"],
// list of files / patterns to load in the browser files: ["src/**/*.js", "test/**/*.js"],
// ....
// preprocess matching files before serving them to the browser // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: { "src/**/*.js": ["webpack"], "test/**/*.js": ["webpack"], },
WARNING in ./node_modules/mocha/lib/mocha.js 217:20-37 Critical dependency: the request of a dependency is an expression @ ./node_modules/mocha/browser-entry.js @ ./test/test.js
WARNING in ./node_modules/mocha/lib/mocha.js 222:24-70 Critical dependency: the request of a dependency is an expression @ ./node_modules/mocha/browser-entry.js @ ./test/test.js
WARNING in ./node_modules/mocha/lib/mocha.js 266:24-35 Critical dependency: the request of a dependency is an expression @ ./node_modules/mocha/browser-entry.js @ ./test/test.js
WARNING in ./node_modules/mocha/lib/mocha.js 313:35-48 Critical dependency: the request of a dependency is an expression @ ./node_modules/mocha/browser-entry.js @ ./test/test.js
WARNING in ./node_modules/mocha/lib/mocha.js 329:23-44 Critical dependency: the request of a dependency is an expression @ ./node_modules/mocha/browser-entry.js @ ./test/test.js ℹ 「wdm」: Compiled with warnings. 12 07 2019 17:07:52.761:WARN [karma]: No captured browser, open http://localhost:9876/ 12 07 2019 17:07:52.770:INFO [karma-server]: Karma v4.1.0 server started at http://0.0.0.0:9876/ 12 07 2019 17:07:52.770:INFO [launcher]: Launching browsers Chrome with concurrency unlimited 12 07 2019 17:07:52.773:INFO [launcher]: Starting browser Chrome 12 07 2019 17:07:54.135:INFO [Chrome 75.0.3770 (Mac OS X 10.14.5)]: Connected on socket R9R31QB1GtR_kayNAAAA with id 55412577 Chrome 75.0.3770 (Mac OS X 10.14.5) LOG: 'hello karma'
LOG: 'hello mocha' Chrome 75.0.3770 (Mac OS X 10.14.5): Executed 1 of 1 SUCCESS (0 secs / 0.001 secs ~/Downloads/karma-demo is 📦 v1.0.0 via ⬢ v10.16.0 ➜ npm run karma
WARNING in ./node_modules/mocha/lib/mocha.js 217:20-37 Critical dependency: the request of a dependency is an expression @ ./node_modules/mocha/browser-entry.js @ ./test/test.js
WARNING in ./node_modules/mocha/lib/mocha.js 222:24-70 Critical dependency: the request of a dependency is an expression @ ./node_modules/mocha/browser-entry.js @ ./test/test.js
WARNING in ./node_modules/mocha/lib/mocha.js 266:24-35 Critical dependency: the request of a dependency is an expression @ ./node_modules/mocha/browser-entry.js @ ./test/test.js
WARNING in ./node_modules/mocha/lib/mocha.js 313:35-48 Critical dependency: the request of a dependency is an expression @ ./node_modules/mocha/browser-entry.js @ ./test/test.js
WARNING in ./node_modules/mocha/lib/mocha.js 329:23-44 Critical dependency: the request of a dependency is an expression @ ./node_modules/mocha/browser-entry.js @ ./test/test.js ℹ 「wdm」: Compiled with warnings. 12 07 2019 17:24:04.905:WARN [karma]: No captured browser, open http://localhost:9876/ 12 07 2019 17:24:04.914:INFO [karma-server]: Karma v4.1.0 server started at http://0.0.0.0:9876/ 12 07 2019 17:24:04.914:INFO [launcher]: Launching browsers Chrome with concurrency unlimited 12 07 2019 17:24:04.922:INFO [launcher]: Starting browser Chrome 12 07 2019 17:24:06.289:INFO [Chrome 75.0.3770 (Mac OS X 10.14.5)]: Connected on socket EbCCSbQRPbxHbq3OAAAA with id 92342032 Chrome 75.0.3770 (Mac OS X 10.14.5) LOG: 'hello karma'
LOG: 'hello mocha' Chrome 75.0.3770 (Mac OS X 10.14.5): Executed 1 of 1 SUCCESS (0 secs / 0.001 secs Chrome 75.0.3770 (Mac OS X 10.14.5): Executed 1 of 1 SUCCESS (0.006 secs / 0.001 secs) TOTAL: 1 SUCCESS
var schema = require('async-validator'); var descriptor = { name: { type: "string", required: true, validator: (rule, value) => value === 'Peanut', }, }; var validator = new schema(descriptor); validator.validate({name: "Peanut"}, (errors, fields) => { if(errors) { // validation failed, errors is an array of all errors // fields is an object keyed by field name with an array of // errors per field
validator.validate({ name: "Peanut", asyncValidator: (rule, value) => axios.post('/nameValidator', { name: value }), }, (errors, fields) => { if(errors) { // validation failed, errors is an array of all errors // fields is an object keyed by field name with an array of // errors per field
var schema = require('async-validator'); var descriptor = { name(rule, value, callback, source, options) { var errors = []; if(!/^[a-z0-9]+$/.test(value)) { errors.push( newError( util.format("%s must be lowercase alphanumeric characters", rule.field))); } return errors; } } var validator = new schema(descriptor); validator.validate({name: "Firstname"}, (errors, fields) => { if(errors) { return handleErrors(errors, fields); } // validation passed });
在需要对一个字段设置多条校验规则时,可以把规则设为一个数组,比如
1 2 3 4 5 6 7 8 9 10 11
var descriptor = { email: [ {type: "string", required: true, pattern: schema.pattern.email}, {validator(rule, value, callback, source, options) { var errors = []; // test if email address already exists in a database // and add a validation error to the errors array if it does return errors; }} ] }
Type 内置类型
下列是 type 可用的值:
string: 必须是 string. This is the default type.
number: 必须是 number.
boolean: 必须是 boolean.
method: 必须是 function.
regexp: 必须是正则或者是在调用 new RegExp 时不报错的字符串.
integer: 整数.
float: 浮点数.
array: 必须是数组,通过 Array.isArray 判断.
object: 是对象且不为数组.
enum: 值必须出现在 enmu 枚举值中.
date: 合法的日期,使用 Date 判断
url: url.
hex: 16进制.
email: 邮箱地址.
Required
required 属性代表这个字段必须出现在对象中
Pattern
pattern 属性代表需要符合的正则
Range
使用 min 和 max 属性定义范围,对于字符串和数组会与 value.length 比较,对于数字会直接与值比较
Length
使用 len 属性直接指定长度,会与字符串和数组的 value.length 比较相等,对于数字会直接与值比较是否相等 如果 len 与 min 和 max 同时使用, len 优先。
var schema = require('async-validator'); var cn = { required: '%s 必填', }; var descriptor = {name:{type: "string", required: true}}; var validator = new schema(descriptor); // deep merge with defaultMessages validator.messages(cn); ...
同时检测有没有缓存,这里的缓存指的是离线缓存(manifest)或者 PWA 设置的缓存并不是咱们平时提到的浏览器强制或者协商缓存,有则读取缓存,否则继续 DNS 解析(这里多说一句:关于这部分缓存可以看一下的WHATWG 的离线缓存规范,WHATWG == Web Hypertext Application Technology Working Group 即网页超文本应用技术工作小组,W3C 宣布与 WHATWG 达成协议,HTML 和 DOM 标准都以 WHATWG 为准,也就是说以后只有一套 HTML 标准了)
// Default to allow up to 6 connections per host. Experiment and tuning may // try other values (greater than 0). Too large may cause many problems, such // as home routers blocking the connections!?!? See http://crbug.com/12066. // // WebSocket connections are long-lived, and should be treated differently // than normal other connections. Use a limit of 255, so the limit for wss will // be the same as the limit for ws. Also note that Firefox uses a limit of 200. // See http://crbug.com/486800 int g_max_sockets_per_group[] = { 6, // NORMAL_SOCKET_POOL 255// WEBSOCKET_SOCKET_POOL };
最后要说的就是,千万不要迷信优化方法,一定要收集完整的数据,找准目标,再做打算,切勿盲目入手。比如现在比较流行的 SSR,也就是服务端渲染,这和以前 JSP 原理一样,不同的时候可以大部分复用相同的前端代码用 Nodejs 在服务端页面 html 渲染好,直接输出到前端。我们可以分析一下这种优化要解决什么问题,在什么情况下才能起到优化的效果。
浏览器端程序无法”流式“加载(非绝对),浏览器中 HTML 就是流式加载的代表:边下载边解析边展示
数据和模板特别复杂,运算量非常大,受众的客户端机能浏览器性能很差,用户端渲染非常慢
第一个场景,如果我们是个长内容网站,内容数据量远远大于我们的静态资源,那么这种方案就很合适,服务端输出的 HTML 内容可以流式加载,不用拿到全量数据就能把第一屏数据及早的展示给用户。但对于我们现在的审批页面,要渲染的内容才几 k,静态资源远远大于内容,这种方式显然就没有什么意义,况且该加载的前端资源一点儿也没少, 渲染部分每个人的输出内容还不同,还丧失了用户端的缓存功能,非常的不划算。