开始测试React Native App(上篇)

前期技术储备

前言


我是测试小白,小小白,小小小白,最近想在成了一定规模的项目中引入测试,于是找了许些资料学习,现在已经在项目中成功引入。于是想在思路明朗和记忆深刻的时候总结下学习路径以及写测试中遇到的难点、坑点、注意点。给自己的近段学习成果做个总结,同时也希望能帮助到和我一样初入测试的人。

注意注意特别注意!!!


React Native在0.56、0.57版本上测试运行有各种各样的问题,例如:Can't run jest tests with 0.56.0、0.56 regression: jest.mock only works when defined in jestSetup.js, not in individual Snapshots tests以及笔者还没遇到的问题,笔者亲测:"Can't run jest tests with 0.56.0"这个问题在0.57中已经解决,“0.56 regression: jest.mock only works when defined in jestSetup.js, not in individual Snapshots tests”这个问题在0.57中依然存在。所以文章示例建议在0.55.4版本中运行。

初入测试一定要明白的重要概念


  • 自动化测试
  • 测试金字塔
  • 单元/集成/e2e测试

扩展阅读:如何自动化测试 React Native 项目 (上篇) - 核心思想与E2E自动化了解以上概念。

随着项目越来越大,新增需求对于开发而言或许不算太大工作量,但是对于测试而言,特别是回归测试,压力会徒然增加很多,如果是手工测试或者是放弃一些测试用例,都是不稳定的测试。所以自动化测试的重要性就体现出来了,自动化测试的大体思路即是”测试金字塔“,测试金字塔从上到下分别是E2E测试、集成测试、单元测试。E2E测试是需要真实编译打包在模拟器上或者真机上模拟用户行为走测试流程,测试结果受网络,弹窗,电话等不可控影响较大,因此不能过于信任,因此E2E测试出的Bug最好能到集成测试中重现,集成测试中的出现的Bug最好能在单元测试中重现,若不能重现则应该加入更多的单元/集成测试来重现Bug。集成和单元测试都不需要编译打包运行,因此它们的执行速度非常快,所以项目中测试代码量应该是单元测试大于集成测试,集成测试大于E2E测试,从而形成自动化测试金字塔。

  • Snapshot
  • Mock
  • JavaScript Testing utility:例如Detox、Enzyme
  • JavaScript Test runners and assertion libraries:例如Jest

文章后面会重点解释以上概念。

React Native对于测试的支持


  • ReactNative官方测试介绍: facebook.github.io/react-nativ…

If you're interested in testing a React Native app, check out the React Native Tutorial on the Jest website.

  • jestjs.io/docs/en/tut…

Starting from react-native version 0.38, a Jest setup is included by default when running react-native init.

通过React NativeJest官方描述,可以得到结论:在react-native 0.38及后续版本在react-native init时已经默认植入了Jest测试库,所以我们可以0配置开始尝试编写测试代码。

使用以下方式开始尝试一下吧 (*^^*) 创建iosandroid同级目录下创建__test__文件夹,在__test__文件夹下创建helloworld.test.js文件,并输入以下代码:

it('test',()=>{expect(42).toEqual(42)
})
复制代码

在终端执行:npm test查看测试结果。 入门是不是超简单o(* ̄ ̄*)o!

注:不是一定要在iosandroid同级的目录创建__test__文件夹才能写测试代码,项目下的*.test.js都可以执行测试。

Jest必备知识


  • Expect
  • Snapshot Testing
  • Mock Functions

请阅读 jestjs.io/docs/en/get… 的 Introduction 章节的前5篇文章(到Mock Function为止),Guides章节的第一篇文章。

Jest 是一个运行测试和断言的库(Test Runner and assertion libraries),Jest通过Expect来断言当前结果和预期结果是否相同,这些结果是这里所涉及到的数据类型。Jest使用Mock来模拟一些Function、Module以及Class来方便测试(Mock测试中不需要真实去执行的代码,例如Fetch,Platform.OS等)。

Snapshot翻译成中文是快照的意思,以前的UI测试是执行测试脚本并在停留的页面上截图,当再次执行相同的测试脚本时会拿前后的截图做对比,如果像素相同则测试通过,像素不相同则测试不通过。在Jest中对React的UI测试可以通过Snapshot生成序列化结构树(文本形式),对比前后生成的结构树即可。Snapshot不仅仅可以用来测试UI,它可以用来测试任何可以序列化的结构,例如Action、Store等,在文章后面会有所提及。

前期技术储备好了我们就可以开始着手写测试了^_^

单元测试

Redux 逻辑测试


官方推荐阅读:Testing React Native with the new Jest — Part II

Redux中的Reducer测试

Reducer是纯函数,也就是说在有相同的输入值时,就一定是相同的输出,因此是很容易测试的。

it('start upload action will combine upload\'s watting queue and failed queue then update upload\'s uploading state', () => {let currentState = Map({'uploadTestKey': new Upload({name: 'uploadTestKey',wattingQueue: List([new UploadItem({name: 'fileTwo',filepath: 'fileTwoPath'})]),uploadedQueue: List([new UploadItem({name: 'fileThree',filepath: 'fileThreePath'}),]),failedQueue: List([new UploadItem({name: 'fileOne',filepath: 'fileOnePath'}),]),})})currentState = UploadReducer(currentState, UPloadActions.startUpload({upload: 'uploadTestKey'}))expect(currentState).toMatchSnapshot()
})
复制代码

上面的代码示例是测试UploadReducer对固定输入currentStateUPloadActions.startUpload({upload: 'uploadTestKey'})的输出是否正确,这里需注意以下两点:

1、要确保第一次运行npm run test后产生的__snapshots__/<测试文件名称>.snap里面内容的正确性。因为expect(currentState).toMatchSnapshot()expect(value).toEqual(someValue)的写法不同,后一种可以在写测试用例时直接给出期望值,前一种是测试用例运行完自动将期望值写入到了__snapshots__/<测试文件名称>.snap文件中,因此在第一次运行完测试用例我们需要确认生成的snapshot的正确性。toMatchSnapshot()的好处是不需要copy代码在测试用例中,如果不使用toMatchSnapshot(),我们的测试用例将写成以下形式:

it('start upload action will combine upload\'s watting queue and failed queue then update upload\'s uploading state', () => {let currentState = Map({'uploadTestKey': new Upload({name: 'uploadTestKey',wattingQueue: List([new UploadItem({name: 'fileTwo',filepath: 'fileTwoPath'})]),uploadedQueue: List([new UploadItem({name: 'fileThree',filepath: 'fileThreePath'}),]),failedQueue: List([new UploadItem({name: 'fileOne',filepath: 'fileOnePath'}),]),})})currentState = UploadReducer(currentState, UPloadActions.startUpload({upload: 'uploadTestKey'}))expect(currentState.is(Map({'uploadTestKey': new Upload({name: 'uploadTestKey',wattingQueue: List([new UploadItem({name: 'fileTwo',filepath: 'fileTwoPath'}),new UploadItem({name: 'fileOne',filepath: 'fileOnePath'}),]),uploadedQueue: List([new UploadItem({name: 'fileThree',filepath: 'fileThreePath'}),]),failedQueue: List([]),})}))).toBe(true)
})
复制代码

这样就造成了代码冗余,这时snapshot的重要性就提现出来了。

2、既然是单元测试,那我们写的每个测试用例的职责都要单一,不要在单元测试中写出集成测试出来,这是刚学测试经常难以区分的。测试的语法并不难,难得是写出什么样的测试用例。例如以上的测试用例是测试一个上传队列组件,它的reducer可以处理多个action,例如pushdeleteupload等,那我们应该怎样为这个reducer写单元测试呢?笔者一开始就跑偏了,写出了这样的测试用例,各位看官可以看看:

describe("upload component reducer test", () => {describe("one file upload", () => {let currentState = Map({})beforeAll(() => {currentState = UploadReducer(currentState, UPloadActions.registerUpload({upload: 'uploadTestKey'}))expect(currentState).toMatchSnapshot()})afterAll(() => {currentState = UploadReducer(currentState, UPloadActions.destroyUpload({upload: 'uploadTestKey'}))expect(currentState).toMatchSnapshot()})...test("handle upload success", () => {let state = UploadReducer(currentState, UPloadActions.pushUploadItem({upload: 'uploadTestKey', name: 'fileOne', filePath: 'fileOnePath'}))expect(state).toMatchSnapshot()state = UploadReducer(state, UPloadActions.startUpload({upload: 'uploadTestKey'}))expect(state).toMatchSnapshot()state = UploadReducer(state, UPloadActions.startuploadItem({upload: 'uploadTestKey'}))expect(state).toMatchSnapshot()state = UploadReducer(state, UPloadActions.uploadItemSuccess({upload: 'uploadTestKey', id: '12345'}))expect(state).toMatchSnapshot()state = UploadReducer(state, UPloadActions.uploadComplete({upload: 'uploadTestKey'}))expect(state).toMatchSnapshot()})test("handler upload failed", () => {...})test("handler reupload success", () => {let state = UploadReducer(currentState, UPloadActions.pushUploadItem({upload: 'uploadTestKey', name: 'fileOne', filePath: 'fileOnePath'}))state = UploadReducer(state, UPloadActions.startUpload({upload: 'uploadTestKey'}))state = UploadReducer(state, UPloadActions.startuploadItem({upload: 'uploadTestKey'}))state = UploadReducer(state, UPloadActions.uploadItemFailed({upload: 'uploadTestKey'}))state = UploadReducer(state, UPloadActions.uploadComplete({upload: 'uploadTestKey'}))expect(state).toMatchSnapshot()state = UploadReducer(state, UPloadActions.startUpload({upload: 'uploadTestKey'}))expect(state).toMatchSnapshot()state = UploadReducer(state, UPloadActions.startuploadItem({upload: 'uploadTestKey'}))state = UploadReducer(state, UPloadActions.uploadItemSuccess({upload: 'uploadTestKey', id: '12345'}))state = UploadReducer(state, UPloadActions.uploadComplete({upload: 'uploadTestKey'}))expect(state).toMatchSnapshot()})})describe("mult file upload", () => {let currentState = Map({})beforeAll(() => {...})afterAll(() => {...})...test("handle upload successed", () => {...})test("handle upload failed", () => {...})test("hanlde reupload successed", () => {...})})
})
复制代码

可以看上以上单元测试的问题吗?在这里引入这篇文章所举的例子:

笔者就是犯了以上错误,测试语法学会后,不知道如何写测试用例,傻傻的在单元测试里写入集成测试,就会出现如果reducer增加了新的action处理,那测试文件中应该添加多少个测试用例呢? 于是笔者改成了以下写法:

describe("upload component reducer test", () => {it('register upload action will register a upload queue to state', () => {let currentState = Map({})currentState = UploadReducer(currentState, UPloadActions.registerUpload({upload: 'uploadTestKey'}))expect(currentState).toMatchSnapshot()})it('destroy upload action will remove upload queue from state', () => {let currentState = Map({'uploadTestKey': new Upload({name: 'uploadTestKey'})})currentState = UploadReducer(currentState, UPloadActions.destroyUpload({upload: 'uploadTestKey'}))expect(currentState).toMatchSnapshot()})it('push upload item action will add an uploadItem into upload\'s wattingQueue', () => {...})it('delete upload item action will remove an uploadItem from upload\'s all queue', () => {...})...
})
复制代码

reducer能处理多少个action就有多少个测试用例,是不是明了多了? 示例代码

Redux中的Action Creator测试

Reducer同样的道理,也是要注意两点,一个是测试用例的职责要对,一定要记住它是“单元测试”,我们只需要保证单个Action creator有特定的输入就有特定的输出,而且要对第一次运行测试用例的输出snapshot进行检查,保证期望值的正确性。 示例代码

如何测试异步Action

通常的Action是一个Object对象,带有type属性即可,但是异步Action它返回的不是一个Object而是一个特殊的Function,需要类似于redux-thunk的中间件来处理。因此我们在测异步Action时需要Mock两个模块,一个是网络异步所需要的fetch,另一个就是可以派发Async ActionStore

请先阅读Jest官方的Mock相关文档:Mock Functions、manual-mocks

Mock fetch可以使用库:jest-fetch-mock Mock store可以使用库:redux-mock-store 具体配置查看官方README,这是配置好的项目。 Object类型的Action测试写法:

it('register upload action' , () => {store.dispatch(UploadActions.registerUpload({upload: 'uploadKey'}))expect(store.getActions()).toMatchSnapshot()
})
复制代码

异步Action测试写法:

it('upload one file fail action test', () => {fetch.mockResponseOnce(JSON.stringify({ error: new Error('fail') }))return store.dispatch(UploadActions.upload('uploadKey', config)).then(() => {expect(store.getActions()).toMatchSnapshot()})
})
复制代码

异步测试有多种写法,分别用来处理callBackPromiseasync/await,具体请查阅官方文档。

Component测试


上面详细讲述了关于Redux的单元测试,下面来看看Component如何做单元测试。

请先阅读Testing React Native with the new Jest — Part I

需要注意的是,网上有许多文章在写组件测试的时候都使用了react-native-mock,用来mock RN的库,但是在RN0.37版本开始,内置于react-native的Jest设置自带一些应用于react-native库的mock。可以在setup.js中查阅,因此不需要再引入react-native-mock。

Component测试的核心点:

  • 给不同的props会有不同的Dom输出。
  • 使用主动执行实例方法来模拟State的变化输出不同的Dom
  • 测试使用connect(component)包裹的组件时,mockconnect组件连接的props直接测试被connect包裹的组件
  • 测试使用HOC的组件时,分别测试ComponentWrapComponent

注意上面列表加粗的文字,这些文字就是我们写Component测试的着手点。

UI Render测试,我们测试的是不同的props有不同的Dom

it('render login screen with init state', () => {const loginWrap = shallow(<LoginScreenhandleSubmit={handleSubmit}valid={false}submitting={false}/>)expect(toJson(loginWrap)).toMatchSnapshot()
})
复制代码

在上段的代码中,我们可以改变valid这些属性值,然后使用toMatchSnapshot来保留snap。这里涉及的库有:enzyme,enzyme-to-json,知识点有:shallow

enzyme是使用javascript语言为react写的测试工具,可以用来快速的获取Component的输出(Dom),操控Dom,以及对Dom写各种断言。类似的有React Test Utilities和react-testing-library,React Test Utilities是React官方出的测试工具,也可以输出Dom,但是它不能操作Dom,没有提供Selector。react-testing-library与enzyme的功能很接近,但是不支持react-native,支持react

enzyme-to-json可以将shallow的结果json化输出,一般配合JesttoMatchSnapshot使用。 Shallow的render方式是浅渲染,只生成Dom树的一层,例如:

//ComponentA.js
import React from 'react'
import {Text,View,
} from 'react-native'class ComponentA extends React.Component {render() {return (<View><ComponentB /></View>)}
}
class ComponentB extends React.Component {render() {return (<Text>Hello world</Text>)}
}export default ComponentA
复制代码
//ComponentA.test.js
import ComponentA from './ComponentA'
import React from 'react'
import { shallow } from 'enzyme'
import toJson from 'enzyme-to-json'it('shallow ComponentA', () => {const wrap = shallow(<ComponentA/>)expect(toJson(wrap)).toMatchSnapshot()
})
复制代码
//ComponentA.test.js.snap
// Jest Snapshot v1, https://goo.gl/fbAQLPexports[`shallow ComponentA 1`] = `
<Component><ComponentB />
</Component>
`;
复制代码

使用Shallow的渲染结果就是<View><ComponentB/></View>,它不会再把ComponentB展开获得<View><Text>Hello world</Text></View>这种结果。这样我们就不用关心子组件的行为,我们之要专心测ComponentA即可。

enzymeenzyme-to-json的安装,参考官网:airbnb.io/enzyme/

UI交互测试,我们需要主动调用实例方法来触发state的更改:

//Foo.js
import React from 'react'
import {Switch
} from 'react-native'export default class extends React.Component {constructor() {super(...arguments)this.state = {value: false}}_onChange = (value) => {this.setState({value: value})}render() {return (<Switch onValueChange={this._onChange} value={this.state.value}/>)}
}
复制代码
//Foo.test.js
import Foo from './Foo'import React from 'react'
import { shallow } from 'enzyme'
import toJson from 'enzyme-to-json'it('Foo change state', () => {const wrap = shallow(<Foo/>)expect(wrap.state(['value'])).toEqual(false)expect(toJson(wrap)).toMatchSnapshot()const firstWrap = wrap.first()firstWrap.props().onValueChange(true)expect(wrap.state(['value'])).toEqual(true)expect(toJson(wrap)).toMatchSnapshot()
})
复制代码
//Foo.test.js.snap
// Jest Snapshot v1, https://goo.gl/fbAQLPexports[`Foo change state 1`] = `
<Switchdisabled={false}onValueChange={[Function]}value={false}
/>
`;exports[`Foo change state 2`] = `
<Switchdisabled={false}onValueChange={[Function]}value={true}
/>
`;
复制代码

在这个例子中,在firstWrap.props().onValueChange(true)前分别打印了snap,并且断言state.value的值,来测试onValueChange引起的state的更改。firstWrap.props().onValueChange(true)就是主动调用实例方法的行为。

HOC测试:

在以上的两个例子中,可以掌握常规组件的单元测试,那么Hoc组件如何测试呢?其实实现方式也很简单,我们把HOC拆开来看,可以分别测Higher OrderComponentComponent的测试和上两个例子一样,需要注意的是,要分别导出Higher OrderComponent以及HOC:

//Hoc.js
import React from 'react'
import {View
} from 'react-native'export function fetchAble(WrappedComponent) {return class extends React.Component{_fetchData = () => {console.log('start fetch')}render() {return (<WrappedComponent fetchData={this._fetchData}/>)}}
}export class Com extends React.Component {render() {return (<ComponentA/>)}
}export default fetchAble(View)
复制代码
//Hoc.test.js
import {fetchAble} from './Hoc'
it('Hoc test', () => {const A = (props) => <View/>const B = fetchAble(A)const fetchWarp = shallow(<B/>)const wrapA = fetchWarp.find(A)expect(wrapA).not.toBeUndefined()expect(wrapA.props().fetchData).not.toBeUndefined()wrapA.props().fetchData()expect(console.log.mock.calls.length).toEqual(1)expect(console.log.mock.calls[0][0]).toEqual('start fetch')
})
复制代码

setupJest中配置了mockconsole

Redux Connect与HOC是同样的道理

组件测试的参考文章(搭梯子):

Sharing and Testing Code in React with Higher Order Components

Testing React Component’s State

Unit Testing Redux Connected Components

这一篇主要是围绕组件和Redux写单元测试,下一篇将开始写集成以及e2e测试

欢迎关注我的简书主页:www.jianshu.com/u/b92ab7b3a… 文章同步更新^_^

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/277224.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql 5中的store function

mysql 5中的store function mysql 5中新增了视图&#xff0c;存储过程&#xff0c;触发器等新功能&#xff0c;其中不少资料对其都有介绍&#xff0c;今天看到了其中的一个叫store function的功能&#xff0c;和存储过程有点象&#xff0c;但返回的是值哦&#xff0c;用法挺灵活…

html frame跳转实例,HTML frame标签怎么用?frame标签的具体使用实例

本篇文章主要为大家讲述的是关于HTML frame标签的用法&#xff0c;还有关于frame标签中的属性介绍。还有关于frame标签的使用实例&#xff0c;下面就让我们一起来看看这篇文章吧首先我们先看看HTML frame标签的用法&#xff1a;标签定义 frameset 中的一个特定的窗口(框架)。fr…

java 实现支持向量机

https://github.com/kevin0016/java-SVM

蓝桥杯——快速排序(2018JavaB组第5题9分)

快速排序&#xff08;18JavaB5,9’&#xff09; 以下代码可以从数组a[]中找出第k小的元素。 它使用了类似快速排序中的分治算法&#xff0c;期望时间复杂度是O(N)的。 请仔细阅读分析源码&#xff0c;填写划线部分缺失的内容。 import java.util.Random; public class Main{pub…

关于蝴蝶背景

Runner 2006-07-04 这些链接都比较久远了&#xff0c;现在大概都失效了。好像有不少人都是这两只可爱的蝴蝶带来的&#xff0c;看来这两只蝴蝶真是我的福星啊。这里再把Flash背景的用法贴出来吧.如果直接点击链接无法显示页面&#xff0c;可以把链接地址复制到浏览器的地址栏里…

html 文本横竖切换,(横竖屏切换/强制横屏)CSS3 transform 怎样才能中心旋转?

现在有一个canvas&#xff0c;我希望在(手机和平板)竖屏时能够把它以中心作为旋转原点旋转90(强制横屏)&#xff0c;但用了transform-origin&#xff0c;无论怎样设置数值都不能达到目的&#xff0c;是我哪里搞错了吗&#xff1f;附CSS代码&#xff1a;html, body{width: 100%;…

Echarts多任务可视化之再优化

1.上次进程可视化由svg实现&#xff0c;本次改用echarts框架实现。Js文件&#xff1a;loadxmldoc.js&#xff08;用于加载xml文档&#xff09;echarts.js&#xff08;用来实现有向图绘制&#xff09;2.思路&#xff1a;Echarts是定义数据对象数组data和连接对象数组links&#…

约 定

十天&#xff0c; 不能见面 &#xff0c;不能打电话或发短信 .这是我和她的约定.十天&#xff0c;要让一个深爱着她的男人去尊照这样的约定去做&#xff0c;实在是难以接受。虽然心里有一万个不愿意&#xff0c;但我知道 &#xff0c;我心里早以做出了选择.选择了我爱的&#x…

NIKE LEBRON 13 PERFORMANCE REVIEW

LeBron James signed a lifetime deal with Nike, cementing his already stable position as Nike’s current top endorser. That means it’s no surprise that Nike would use a person in such a position to help lead new innovative technology. But at times, Nike is…

我的专业我的梦作文计算机,我的创新我的梦作文

新时代&#xff0c;新变化。现在二十一世纪&#xff0c;人们都过上了幸福的生活&#xff0c;发明出来的日常用品&#xff0c;电子产品等等&#xff0c;都是根据人们创新的精神建造出来的。现在我们通过了学习&#xff0c;也渐渐体会到了创新的重要性。就像是在动画片中&#xf…

Ubuntu挂载新硬盘

1、加挂硬盘 Shell代码 复制代码sudo hdparm -I /dev/sdb 硬盘硬件安装后&#xff0c;此命令测试linux系统是否能找到挂载的未分区硬盘 2、创建分区 Shell代码 复制代码sudo fdisk /dev/sdb sda是第一块SCSI硬盘&#xff0c;sdb第二块&#xff0c;以此类推...物理分区使用a、b…

利用XML实现通用WEB报表打印 卢彦

利用XML实现通用WEB报表打印(1) 卢彦 摘要开发B/S结构的应用程序最头疼的问题可能就是报表打印了&#xff0c;由于只能采用浏览器来作为用户界面进行交互&#xff0c;所以不能精确控制客户端的打印机。而很多B/S结构的应用程序常常需要完成非常复杂的报表打印任务。而靠IE自带的…

增加新分类daily——“每天学了啥?”

如题转载于:https://www.cnblogs.com/sig3/p/3926538.html

现代计算机密码学阶段主要有两个方向,密码学技术读书笔记

关于密码学技术读书笔记一.密码学的介绍密码学(在西欧语文中&#xff0c;源于希腊语krypts“隐藏的”&#xff0c;和grphein“书写”)是研究如何隐密地传递信息的学科。在现代特别指对信息以及其传输的数学性研究&#xff0c;常被认为是数学和计算机科学的分支&#xff0c;和信…

cascade rcnn论文总结

1.bouding box regression总结&#xff1a; rcnn使用l2-loss 首先明确l2-loss的计算规则&#xff1a; L∗(f∗(P)−G∗)2&#xff0c;∗代表x,y,w,h 整个loss : LLxLyLwLh 也就是说&#xff0c;按照l2-loss的公式分别计算x,y,w,h的loss&#xff0c;然后把4个loss相加就得到总的…

浅谈优化SQLServer数据库服务器内存配置的策略

引文 http://demo.nbarticle.com/view/2004/8/9/view_1827.htm 农业银行总行1998年以来正式推广了新版网络版综合业务统计信息系统&#xff0c;该系统是基于WindowsNT4.0平台&#xff0c;采用客户&#xff0f;服务器模式&#xff0c;以Microsoft SQL Server为基础建立起来的大…

知道第一章计算机基础知识作业答案,大学计算机基础作业答案

大学计算机基础作业答案第一章 现代社会与计算机1. 什么事信息&#xff0c;其主要特征是什么&#xff1f;答&#xff1a;信息是可传递和共享的&#xff0c;可消除人们认知上的不确定因素&#xff0c;对人们的决策具有现实或潜在价值的知识。特征&#xff1a;普遍性、依附性、共…

Netflix推荐系统(Part two)-系统架构

Netflix在2013年公布了自己推荐系统的架构&#xff0c;本文主要总结和翻译自System Architectures for Personalization and Recommendation&#xff0c;但这并不是一篇完整的翻译文章。 Overview 首先&#xff0c;我们在下图中提供推荐系统的整体系统图。 该体系结构的主要组件…

母版页可以动态切换吗?

通过设置“MasterPageFile”属性可以做到&#xff0c;然而这个属性只能在“Page_PreInit”事件之中或之前设置。在Page_PreInit事件或之前&#xff0c;当前页面包含的对象还没有被生成&#xff0c;不能访问&#xff0c;所以&#xff0c;如果想根据当前页面上某个控件的值动态切…

httpclient 多附件上传

多附件上传实例&#xff1a; /*** 多附件上传* param host* param uri* param attachment 附件* param param body参数* return*/public String upload(String host, String uri, Map<String,String> attachment, Map<String, String> param) {logger.info("…