首页 文章 精选 留言 我的

精选列表

搜索[AI技能],共10000篇文章
优秀的个人博客,低调大师

什么技能产品经理不会提,但技术人必须懂?

阿里妹导读:缓存是搭建高性能高并发系统的必备手段之一,通常用来解决性能瓶颈,是程序员的必备知识点,也是面试必备考点。 尽管,产品经理大概率不会关注系统性能,但程序员在实现需求的时候必须思考系统承载的并发量和用户量。缓存主要用来解决性能瓶颈的问题,一旦错误使用反而会令系统崩溃。今天,我们就通过4W的方式系统化地总结缓存相关的理论知识。 随着互联网业务的快速迭代以及用户量激增,应用架构需要不断调整甚至重构以适应这种业务的快速发展。当数据量迅速增长,业务逻辑越复杂,服务链路不断增加等等一系列问题,会导致RT过长,服务性能需要逐渐提升以满足更优的用户体验。在优化系统架构时通常的所用的两种方式scale up以及scale out,scale out就是通常所说的水平扩展,将应用服务设计成无状态性,可以方便水平扩展通过增加硬件的方式分解访问压力。而scale up则是将单个服务链路性能提升,以提升QPS以及系统的吞吐量。在追求更优的性能时,大多数业务场景是读多写少的情况,一般会通过引入缓存的方式解决。 1. What——什么是缓存? 从定义上可以看出所谓缓存一定是针对已有数据的一个副本存在,也可以看出缓存的使用是为了解决快速访问数据(读数据)的场景。在现有的互联网应用中,缓存的使用是一种能够提升服务快速响应的关键技术,也是产品经理无暇顾及的非功能需求,需要在设计技术方案时对业务场景,具有一定的前瞻性评估后,决定在技术架构中是否需要引入缓存解决这种这种非功能需求。 缓存在计算机领域中实际案例存在很多,比如CPU的缓存是为了解决CPU的运算速度和内存的读取数据不平衡的问题,CPU的运算速度远快与内存的读写速度,为了降低CPU等待数据读写的时间,在CPU中引入L1/L2/L3多级缓存。 再比如Linux中的文件缓存,实际上我们在编程时,会谈论到数据的内存地址,但是我们接触的都是虚拟地址而不是真实的物理地址,计算机中的内存管理单元(MMU)和页表会将虚拟地址转换成物理地址。在计算机硬件领域中就已有很多关于缓存的应用案例,实际上在软件架构中关于缓存的设计会借鉴于很多传统且成熟的计算机硬件缓存设计的思想。 2. Why——为什么需要使用缓存? 软件服务能够得到用户的信赖,并将产品的价值带给用户,能够解决目标用户的痛点问题这是决定用户会不会一开始决定使用,也就是《增长黑客》中提到了产品能够带来给用户“啊哈时刻”,而决定用户会不会高频使用以及持续使用,用户体验则是被认为是软件产品提升用户黏性的关键影响因素。 2.1 什么是用户体验 用户体验被专业定义和推广需要推广到20世纪90年代,由Donald Norman布道推广。用户体验在人机交互领域上受到了重视,并一度和传统的三大可用性指标(即效率、效益以及基本满意度)不相上下。 ISO 9241-210标准将用户体验官方定义为:人们对正在使用或者期待使用的产品、系统或者服务的认知印象和回应。可以看出用户体验是用户的对软件产品的主观感受,具体包含了用户在使用之前、使用中以及使用后的情感、喜好、认知印象、心理反应以及情绪表达等等多种主观感受,每个用户对产品的主观感受的视角不同,关注点不同,也就导致软件产品让大多数用户都能够获得很好的用户体验本身就是一件很有挑战性的事情。 在业界大多数,将用户体验分为三类:使用者状态、软件产品的系统性能以及环境,使用者状态以及环境(使用者环境以及产品在同类产品中的产品大环境)这两个因素需要交互设计和用研等多个专业领域同学去攻克,软件开发者则需要解决系统性能的问题。对用户而言,最基本的需求就是在使用软件服务时,软件产品提供服务内容的及时性,也就是通常所说的在使用过程中持续的Loading(转菊花)一定会导致用户体验很差,内容的及时性也是系统性能的最低要求。 而系统性能的问题,是产品经理无暇顾及的点,也是非功能性需求,需要开发者去花心思去思考的地方。评估系统性能的指标有很多,在以提升用户体验为前提的情况下,我们需要着重关注的性能指标有哪些呢? 2.2 常见的性能指标 在设计软件架构时需要关注的几个常见指标:响应时间、延迟时间、吞吐量、并发用户数和资源利用率。 1)系统响应时间 :响应时间是指系统对用户请求做出响应的时间,不同的功能的链路长短不同,并且同一功能在不同数据量等这些情况都会导致响应时间的不同。因此,在衡量系统响应时间时,通常会关注软件产品所有功能的平均响应时间以及最大响应时间。 2)延迟时间 :在讨论系统响应时间时,更细粒度的划分可以划分为: 客户端在接受数据进行渲染的内容“呈现时间”; 服务端在接受用户请求发送至服务端以及服务端将数据返回到客户端这两个过程中涉及到的:网络传输时间以及应用延迟时间。应用延迟时间即是服务端在执行整个服务链路时所花费的时间,也是性能优化首要降低的就是这个时间。 3)吞吐量 :吞吐量指的是单位时间内能够处理请求的数量,对于无并发的应用来说,吞吐量和请求响应时间成反比,服务延迟更长则系统吞吐量更低。 4)并发用户数 :并发用户数指的是系统能够同时承载正常使用系统功能的用户数,相较于吞吐量,这个指标更为笼统但是对于非软件领域的人来说更容易理解。 5)资源利用率 :资源利用率反映的是在一段时间内资源被占用的情况。 2.3 缓存带来的优势 在追求更优的优化体验时,客观的来说需要不断提升以上这些性能指标,不断逼近系统体验的最优解。缓存到底具有什么样的优势,值得我们花费很大的精力去设计一套能很好的适应现在的业务场景的缓存结构呢? 1)极大的提升软件用户体验 软件产品主要围绕两个核心问题,一是解决目标用户的痛点问题,二是提升产品黏性。在提供软件服务时,抽象的来看是解决数据在整个链路上的流转问题,如何让数据流转更加高效、更加顺畅是在实现时着重关注的地方,事实上,无论是浏览器、负载均衡、应用服务器还是数据库等等各个环节都会应用到缓存,当数据离用户“更近”,比如数据副本在客户端上,也就意味着请求能够很快的进行响应,相应的给用户进行数据呈现的耗时就更短。现如今用户爸爸们“日理万机”,如果一个软件产品不能在很短时间就获取用户的注意力,很大可能性就意味着失败。因此,使用缓存能够让用户从主观上获取更优的用户体验。 2)提升吞吐量 试想,如果在服务链路上,请求能够在缓存中获取服务数据的话,也就意味着很多数据并不需要从源应用服务器进行获取,降低了源服务器网络传输的频率,在一定IDC带宽下,系统能够降低网络传输时间以及应用延迟时间,从而支撑更多的系统访问以提升系统整体吞吐量以及并发用户数,硬件的使用效率也会明显提升。 从实际场景下,在系统性能优化时大概率会优先选择使用缓存进行系统优化,也是一种被证明有效的手段,缓存也被认为是一种“空间换时间”的艺术。 3. Where——缓存存在链路中的哪些地方? 3.1 缓存分类 从一个请求到最终获取响应,会经过很多环节,缓存可以几乎存在整个链路的每个节点。缓存按照不同的维度可以有如下分类: 1)缓存所处链路节点的位置:客户端缓存 网络缓存 服务端缓存 2)缓存架构部署方式:单机缓存 缓存集群 分布式缓存 3)缓存的内存区域本地缓存/进程内缓存 进程间缓存 远程缓存 按照缓存在服务链路上的位置来划分,可以系统性的梳理下缓存的不同应用。 3.2 客户端缓存 客户端缓存是离用户“最近”的一种存储介质,经常和网络测和服务端缓存一起配合使用,常见的客户端缓存有如下几种: 1)页面缓存:页面缓存是指将静态页面获取页面中的部分元素缓存到本地,以便下次请求不需要重复资源文件,h5很好的支持的离线缓存的功能,具体实现可通过页面指定manifest文件,当浏览器访问一个带有manifest属性的文件时,会先从应用缓存中获取加载页面的资源文件,并通过检查机制处理缓存更新的问题。 2)浏览器缓存:浏览器缓存通常会专门开辟内存空间以存储资源副本,当用户后退或者返回上一步操作时可以通过浏览器缓存快速的获取数据,在HTTP 1.1中通过引入e-tag标签并结合expire、cache-control两个特性能够很好的支持浏览器缓存,关于浏览器缓存更为细节的知识可以查看该文章。 3)APP缓存:APP可以将内容缓存到内存或者本地数据库中,例如在一些开源的图片库中都具备缓存的技术特性,当图片等资源文件从远程服务器获取后会进行缓存,以便下一次不再进行重复请求,并可以减少用户的流量费用。 客户端缓存是前端性能优化的一个重要方向,毕竟客户端是距离“用户”最近的地方,是一个可以充分挖掘优化潜力的地方。 3.3 网络缓存 网络缓存位于客户端以及服务端中间,通过通过代理的方式解决数据请求的响应,降低数据请求的回源率。通常具有如下几种形式的网路缓存: 1)web代理缓存:常见的代理形式分为分为:正向代理、反向代理以及透明代理。web代理缓存通常是指正向代理,会将资源文件和热点数据放在代理服务器上,当新的请求到来时,如果在代理服务器上能获取数据,则不需要重复请求到应用服务器上; 2)边缘缓存:和正向代理一样,反向代理同样可以用于缓存,例如nginx就提供了缓存的功能。进一步,如果这些反向代理服务器能够做到和用户请求来自同一个网络,那么获取资源的速度进一步提升,这类的反向代理服务器可以称之为边缘缓存。常见的边缘缓存就是CDN(Content Delivery Network),可以将图片等静态资源文件放到CDN上。 3.4 服务端缓存 服务端缓存是后端开发中进行性能优化的发力点,常见的后端性能优化也是通过引入缓存来进行解决,常见的有数据库的查询缓存、缓存框架以及引入应用级缓存。 3.4.1 数据库查询缓存 例如,MySQL的缓存机制是通过将SELECT语句以及相应的ResultSet进行缓存,当后续接受到SELECT请求后,如果MySQL已经开启了Query Cache功能,会将SELECT语句以字符串的方式进行hash,然后去从缓存中进行查询,如果查询出数据,则直接进行返回,省去了后续的优化器以及存储引擎IO的操作,能够极大的提升响应时效。如何优化Query Cache需要从如下几个指标上进行考虑: query_cache_size:设置能够缓存ResultSet的内存区域大小query_cache_type:表示使用缓存的场景。0表示任何场景下都不使用Query Cache,1表示显式指定不使用Query Cache的查询都可以使用,2(DEMAND)表示只有明确指示使用Query Cache才会生效;Qcache hits:表示多少次查询命中Query CacheQcache inserts:表示多少次没有命中Query Cache而插入数据Qcahce lowmem prunes:表示多少条Query引入空间不足而被清除Qcache free memory:表示剩余内存大小Qcache free blocks:该值很大表示内存碎片很多,需要及时清理 在进行Qcache优化时,可以对以上指标综合进行分析,比如了解Qcache的缓存命中率 = Qcache hits/ Qcache hits + Qcache inserts,来判断当前Qcache的效率。也可以结合Qcahce lowmem prunes、Qcache free memory以及Qcache free blocks来判断当前Qcache的内存使用效率。 另外,如果使用Innodb存储引擎的话,也需要着重关注innodb_buffer_pool_size参数,该参数决定了innodb的索引以及数据是否有足够大的空间放入到缓存中。table_cache决定了能够缓存表的最大数量,也是需要关注的一个参数。 3.4.2 缓存框架 在功能开发时,会常用提供缓存特性的缓存框架或者实现缓存功能的类库来高效的完成开发,常见的缓存框架有Ehcache、Guava等,这些缓存框架配置简单,能够简单灵活的使用。这些开源的缓存框架不仅支持单机的本地缓存还能配置集群的方式达到灵活伸缩。 3.4.3 应用级缓存 当缓存框架不能满足需求的时候,就需要引入应用级缓存,比如Redis、MongoDB等NoSQL数据库,应用级缓存具备高可用性以及伸缩性的分布式架构能够支撑业务需求,当然,做好一款应用级缓存产品其中的挑战也是巨大。 4. When——什么时候需要使用缓存? 缓存不是架构设计的必选项,也不是业务开发中的必要功能点,只有在业务出现性能瓶颈,进行优化性能的时候才需要考虑使用缓存来提升系统性能。也不是所有的业务场景都适合使用缓存,读多写少且数据时效要求越低的场景越适合使用缓存,缓存并不是所有性能问题的灵丹妙药,如果滥用缓存反而会成为毒药,并且会引入维护缓存的操作成本,使得系统复杂度更高不利于维护。 另外把缓存当做存储来使用是一件极其致命的做法,这种错误的认识,将缓存引入系统的那一刻起就意味着已经让系统走上了危险的局面,对缓存的使用边界要有深刻的理解,才能尽可能保证做出引入缓存才是一个正确的决定。 在进行缓存结构设计的时候,需要考虑的点有很多: 1)业务流量量级以及应用规模:对于低并发低流量的应用而言,引入缓存并不会带来性能的显著提升,反而会带来应用的复杂度以及极高的运维成本。也不是任何数据都需要使用缓存,比如图片视频等文件使用分布式文件系统更合适而不是缓存。因此,在引入缓存前,需要对当前业务的流量进行评估,在高并发大流量的业务场景中引入缓存相对而言收益会更高; 2)缓存应用的选择:缓存应用有很多如Redis、Memcached以及tair等等,针对每一种分布式缓存应用的优缺点以及适用范围、内存效率、运维成本甚至团队开发人员的知识结构都需要了解,才能做好技术选型; 3)缓存影响因素的正确评估:在引入缓存前,需要着重评估value大小、缓存内存空间、峰值QPS、过期时间、缓存命中率、读写更新策略、key值分布路由策略、过期策略以及数据一致性方案等等多个因素,要做到心中有数; 4)缓存高可用架构:分布式缓存要高可用,这也是分布式系统追求的三高指标中的一个,缓存的集群设计,主从同步方案的设计等等,只有缓存足够可靠,才能服务于业务系统,为业务带来价值; 5)完善的监控平台:当缓存投入生产环境后,需要有一套监控系统能够显式的观测缓存系统的运行情况,才能更早的发现问题,同时对于预估不足的非预期热点数据,也需要热点发现系统去解决非预期的热点数据缓存问题。 6)缓存最近原则:将缓存数据放在离用户最近的地方,无疑会极大的提升响应的速度,这也是多级缓存设计的核心思想。 原文发布时间为:2019-11-25作者: 阿里技术本文来自云栖社区合作伙伴“阿里技术”,了解相关信息可以关注“阿里技术”。

优秀的个人博客,低调大师

做大数据工程师需要掌握哪些技能呢?

大数据是眼下非常时髦的技术名词,与此同时自然也催生出了一些与大数据处理相关的职业,通过对数据的挖掘分析来影响企业的商业决策。Hadoop工程师用人企业普遍要求掌握以下技术:1、熟悉Linux开发环境,熟悉Shell命令2、熟悉Java、python、scala语言(至少一种)3、具备较丰富的基于Hadoop、Map Reduce、Yarn、Storm、Spark、Hive、Hbase、kafka、Flume、HDFS、Spark Streaming等的大数据处理项目经验。 在这里还是要推荐下我自己建的大数据学习交流群:199427210,群里都是学大数据开发的,如果你正在学习大数据 ,小编欢迎你加入,大家都是软件开发党,不定期分享干货(只有大数据软件开发相关的),包括我自己整理的一份最新的大数据进阶资料和高级开发教程,欢迎进阶中和进想深入大数据的小伙伴加入。 于是每家公司对大数据工作的要求不尽相同:有的强调数据库编程、有的突出应用数学和统计学知识、有的则要求有咨询公司或投行相关的经验、有些是希望能找到懂得产品和市场的应用型人才。正因为如此,很多公司会针对自己的业务类型和团队分工,给这群与大数据打交道的人一些新的头衔和定义:数据挖掘工程师、大数据专家、数据研究员、用户分析专家等都是经常在国内公司里出现的Title,我们将其统称为“大数据工程师”。 由于国内的大数据工作还处在一个有待开发的阶段,因此能从其中挖掘出多少价值完全取决于工程师的个人能力。已经身处这个行业的专家给出了一些人才需求的大体框架,包括要有计算机编码能力、数学及统计学相关背景,当然如果能对一些特定领域或行业有比较深入的了解,对于其快速判断并抓准关键因素则更有帮助。 目前国内的大数据应用多集中在互联网领域,有超过56%的企业在筹备发展大数据研究,“未来5年,94%的公司都会需要数据科学家。” 大数据只需要学习Java的标准版JavaSE就可以了,像Servlet、JSP、Tomcat、Struct、Spring、Hibernate,Mybaits都是JavaEE方向的技术在大数据技术里用到的并不多,只需要了解就可以了,当然Java怎么连接数据库还是要知道的,像JDBC一定要掌握一下,有同学说Hibernate或Mybaits也能连接数据库啊,为什么不学习一下,我这里不是说学这些不好,而是说学这些可能会用你很多时间,到最后工作中也不常用,我还没看到谁做大数据处理用到这两个东西的,当然你的精力很充足的话,可以学学Hibernate或Mybaits的原理,不要只学API,这样可以增加你对Java操作数据库的理解,因为这两个技术的核心就是Java的反射加上JDBC的各种使用。 Linux:因为大数据相关软件都是在Linux上运行的,所以Linux要学习的扎实一些,学好Linux对你快速掌握大数据相关技术会有很大的帮助,能让你更好的理解hadoop、hive、hbase、spark等大数据软件的运行环境和网络环境配置,能少踩很多坑,学会shell就能看懂脚本这样能更容易理解和配置大数据集群。还能让你对以后新出的大数据技术学习起来更快。 好说完基础了,再说说还需要学习哪些大数据技术,可以按我写的顺序学下去。 Hadoop:这是现在流行的大数据处理平台几乎已经成为大数据的代名词,所以这个是必学的。Hadoop里面包括几个组件HDFS、MapReduce和YARN,HDFS是存储数据的地方就像我们电脑的硬盘一样文件都存储在这个上面,MapReduce是对数据进行处理计算的,它有个特点就是不管多大的数据只要给它时间它就能把数据跑完,但是时间可能不是很快所以它叫数据的批处理。YARN是体现Hadoop平台概念的重要组件有了它大数据生态体系的其它软件就能在hadoop上运行了,这样就能更好的利用HDFS大存储的优势和节省更多的资源比如我们就不用再单独建一个spark的集群了,让它直接跑在现有的hadoop yarn上面就可以了。其实把Hadoop的这些组件学明白你就能做大数据的处理了,只不过你现在还可能对"大数据"到底有多大还没有个太清楚的概念,听我的别纠结这个。等以后你工作了就会有很多场景遇到几十T/几百T大规模的数据,到时候你就不会觉得数据大真好,越大越有你头疼的。当然别怕处理这么大规模的数据,因为这是你的价值所在,让那些个搞Javaee的php的html5的和DBA的羡慕去吧。 Zookeeper:这是个万金油,安装Hadoop的HA的时候就会用到它,以后的Hbase也会用到它。它一般用来存放一些相互协作的信息,这些信息比较小一般不会超过1M,都是使用它的软件对它有依赖,对于我们个人来讲只需要把它安装正确,让它正常的run起来就可以了。 Mysql:我们学习完大数据的处理了,接下来学习学习小数据的处理工具mysql数据库,因为一会装hive的时候要用到,mysql需要掌握到什么层度那?你能在Linux上把它安装好,运行起来,会配置简单的权限,修改root的密码,创建数据库。这里主要的是学习SQL的语法,因为hive的语法和这个非常相似。 Sqoop:这个是用于把Mysql里的数据导入到Hadoop里的。当然你也可以不用这个,直接把Mysql数据表导出成文件再放到HDFS上也是一样的,当然生产环境中使用要注意Mysql的压力。 Hive:这个东西对于会SQL语法的来说就是神器,它能让你处理大数据变的很简单,不会再费劲的编写MapReduce程序。有的人说Pig那?它和Pig差不多掌握一个就可以了。 Oozie:既然学会Hive了,我相信你一定需要这个东西,它可以帮你管理你的Hive或者MapReduce、Spark脚本,还能检查你的程序是否执行正确,出错了给你发报警并能帮你重试程序,最重要的是还能帮你配置任务的依赖关系。我相信你一定会喜欢上它的,不然你看着那一大堆脚本,和密密麻麻的crond是不是有种想屎的感觉。 Hbase:这是Hadoop生态体系中的NOSQL数据库,他的数据是按照key和value的形式存储的并且key是唯一的,所以它能用来做数据的排重,它与MYSQL相比能存储的数据量大很多。所以他常被用于大数据处理完成之后的存储目的地。 Kafka:这是个比较好用的队列工具,队列是干吗的?排队买票你知道不?数据多了同样也需要排队处理,这样与你协作的其它同学不会叫起来,你干吗给我这么多的数据(比如好几百G的文件)我怎么处理得过来,你别怪他因为他不是搞大数据的,你可以跟他讲我把数据放在队列里你使用的时候一个个拿,这样他就不在抱怨了马上灰流流的去优化他的程序去了,因为处理不过来就是他的事情。而不是你给的问题。当然我们也可以利用这个工具来做线上实时数据的入库或入HDFS,这时你可以与一个叫Flume的工具配合使用,它是专门用来提供对数据进行简单处理,并写到各种数据接受方(比如Kafka)的。 Spark:它是用来弥补基于MapReduce处理数据速度上的缺点,它的特点是把数据装载到内存中计算而不是去读慢的要死进化还特别慢的硬盘。特别适合做迭代运算,所以算法流们特别稀饭它。它是用scala编写的。Java语言或者Scala都可以操作它,因为它们都是用JVM的。

优秀的个人博客,低调大师

Java高级编程细节-动态代理-进阶高级开发必学技能

关于代理模式的话题有很多, 在开发中经常用到的应该是静态代理模式,能很好的去耦合。 动态代理是代理模式的另外一种实现。 动态代理的区别在哪里? 动态代理有什么好处? 今天我们来分析下这些问题。 回顾静态代理 之前我们分析过一次静态代理, 用代理模式优雅地写代码 一个典型的代理模式的 Proxy类像下面这样, 对于调用者来说,需要把构造好的实例传给代理,然后就可以用代理来替代操作真正的实例了。 静态代理的问题是, 在接口代码少的情况下一切没什么问题,但是当接口增加的时候, Proxy 类就需要响应的增加接口,比方上面的 Func 接口, 刚开始可能只有一个 read()方法,后面慢慢发展到有了 write(),有了 mark(), 随着接口量的增加, Proxy的维护工作量也在逐步增加。 那么动态代理能怎么解决这种问题呢? 动态代理的实现 动态代理的实现步骤基本如下: · 定义一个公共接口(像 Func)和实现类(像 User),这部分跟静态代理一样 · 定义一个 DynamicProxy类实现 InvocationHandler 接口,这个Proxy类似于静态代理的 Proxy然而不用实现 Func接口 · 调用 Proxy类来构造代理对象 在动态代理的实现中有两个东西非常重要,一个是 Proxy类,一个是 InvocationHandler接口,都位于 reflect包下, 我们来看第二个步骤所定义的 DynamicProxy是怎样的吧 它同样持有了委托对象实例,但是和静态代理不同, 它并没有实现委托对象的接口方法, 而只实现了 InvocationHandler的 invoke方法,然后调用了 method.invoke(this.user, objects); 可以留意下 invoke方法,后面我们继续分析, 这里再贴一下 Client类的代码,也就是使用 Proxy的地方, 跟静态代理不同的地方在于,虽然这里也需要实例化一个委托类的对象,并传给 Proxy的构造方法, 但这里所实例化的是 InvocationHandler对象,而不是 DynamicProxy的对象。 到这里就完成了一个动态代理的代码,输出结果如下 before invoke user method user read after invoke user method 比较&分析 · 先来说第二个步骤的 DynamicProxy的实现, 可以发现跟静态代理不同的地方在于,静态代理需要实现 Func接口的 read()方法,而动态代理实现的是 InvocationHandler的 invoke方法, 静态代理需要在不同的接口中去调用 User 接口的不同方法, 而动态代理在invoke被调用的过程中不需要关心需要调用 User 的哪个具体方法, 方法被封装在 method对象中,而所需要的参数则在 Object[] objects, 直接调用就可以 这里就意味着即使以后增加了 Func的接口,对于 DynamicProxy来说也不需要增加额外的维护量。 · Proxy.newProxyInstance干了什么 在静态代理里面,我们会直接用 new StaticProxy(user)构造出来的静态对象直接操作, 而在动态代理里面,我们操作的是 Proxy.newProxyInstance所构造出来的动态代理对象 proxyUser, 可能初次接触动态代理的同学在这里就概念混乱了, "难道代理类不是 new DynamicProxy出来的对象吗?" 其实不是的,如果把 proxyUser的类名打印出来的话, 它会以 $ProxyN的形式存在,N从0开始,这个就是动态代理所生成的真正代理对象, 动态代理的意义就在于这里,$ProxyN 这个对象是在运行时创建的, 如果用代码来解释的话,$ProxyN的代码会像下面这样 这个才是真正的代理类。 欢迎工作一到五年的Java工程师朋友们加入Java架构开发:468947140 点击链接加入群聊【Java-BATJ企业级资深架构】:https://jq.qq.com/?_wv=1027&k=5zMN6JB 本群提供免费的学习指导 架构资料 以及免费的解答 不懂得问题都可以在本群提出来 之后还会有职业生涯规划以及面试指导 对本文感兴趣欢迎关注笔者,如有不同意见欢迎留言。

优秀的个人博客,低调大师

Java架构师必会的技能(你都会了吗?)

我把它分为了五大专题 工程化专题 工程化专题 git git安装使用 git日常使用:fetch/pull/push/revert/rebase git分支管理git flow Jenkins多套环境(test/pre/production)系统自动化发布 Jenkins自动发布到远程主机 MavenMaven私服搭建setting.xml文件剖析pom.xml详解Maven实用插件教学(静态代码检查、生成可执行jar包)profile使用 源码分析 源码分析 Spring源码分析 Spring IOC的实现原理Spring BeanFactory源码分析Spring AOP的实现原理及配置文件详解Spring AOP的各种应用场景分析Spring MVC与Struts对比Spring HandlerMapping详解手写实现SpringMVC框架Spring与各种框架集成原理Spring JDBC操作原理基于Spring JDBC手写ORM框架 MyBatis源码分析 MyBatis3简介MyBatis3 SqlMap那些事儿数据库连接池到底是什么MyBatis3 SessionFactory实现原理MyBatis3 配置文件详解MyBatis3 事务管理与集成浅谈HibernateMyBatis3与Hibernate框架对比Netty源码分析 NIO通信原理剖析深入了解NIO缓冲区Buffer NIO Selector原理AIO编程Netty产生的背景以及基础入门 Netty高性能之道Netty的HTTP与Socket通信原理利用Netty搭建高性能的 WebSocket聊天室 Netty聊天室客户端架构实现Netty的编码解码 Netty的拆包粘包操作MsgPack原理讲解及各种序列化框架对比MsgPack与Netty整合 Netty HTTP通信与Spring整合Netty RPC架构Netty与各种架构整合以及Netty源码分析 性能调优 性能调优 JVMJVM内存模型JVM运行时数据区垃圾回收机制GC日志详解 根据GC日志调优系统,调优不靠碰运气!Mysql数据库优化 数据库底层数据结构索引数据存储结构 innodb详解SQL调优及原理分库、分表实现Nginx调优动静资源分离 nginx参数详解nginx + lua使用应用:ip过滤,扛DDOSTomcat调优 Tomcat源码、架构分析Tomcat具体调优参数设置Tomcat压力基准测试Tomcat NIO配置 双十一技术架构专题-九阳真经 九阳真经技术专题 秒杀系统实战 从0到1构建秒杀系统从all-in-one到分布式从1到100的优化重构分布式压测系统 最简单的单机压测压测应该关注的点如何做分布式压测分布式跟踪系统 如何梳理链路依赖如何做到优雅降级分布式配置系统 灵活配置热发布一切都是为了自动化分布式消息系统 异步化&系统隔离应对瞬时大流量分布式缓存体系 五级缓存体系热点数据处理 高性能及分布式专题 分布式专题分布式缓存技术 RedisRedis简介及安装Redis基础知识、数据类型、Keys的操作命令Redis对不同数据类型的操作命令使用Redis核心配置分析Redis持久化概述,RDB原理、AOP原理分析Redis事务操作分析以及发布订阅模式的操作使用Redis集群搭建,一步步教你如何搭建Redis集群Redis主从复制原理分析Redis的优化建议、最佳实践Redis的JAVA客户端使用Redis集成Spring的使用MemcachedMemcached的介绍和安装、基本配置、常用运维命令 Memcached的工作原理Memcached的常用操作命令使用分析理解Memcached的数据存储方式,理解Slab Allocator、新建Item分配内存的过程Memcached数据存储方式的缺点;分析Memcached的数据过期方式Memcached内存调优建议、常见问题的解决方案Memcached的典型应用场景、不适用的场景Memcached基于Java客户端的使用分布式接口技术 DubboDubbo的发展过程及基本原理Dubbo控制台及监控平台的搭建使用快速搭建Dubbo服务框架Dubbo常用配置的使用及分析Dubbo注册中心原理、整合Zookeeper实现服务动态发现Dubbo多注册中心、多协议支持Dubbo服务分组、服务版本号控制Dubbo并发控制、连接控制Dubbo负载均衡、集群容错使用和分析Dubbo配置文件优先级别Dubbo和DubboX的比较以及DubboX的使用介绍ThriftThrift基本介绍、 如何安装配置ThriftThrift的数据类型、基本类型、结构体类型、容器类型介绍通过Thrift搭建服务端客户端通信Thrift数据传输协议分析 Restful什么是RestfulRestful的起源如何实现一个满足Restful架构的设计Restful实战 分布式服务协调 技术 Zookeeper 什么是Zookeeper、Zookeeper的安装配置、常用命令使用Zookeeper节点特性以及节点属性分析如何搭建zookeeper的集群环境Zookeeper客户端的使用:zkclient/curatorZookeeper的实现原理分析Zookeeper实战,共享锁、 master选举Consoul Eureka 微服务架构Spring BootSpring Boot概述、如何快速搭建一个Spring Boot的项目Spring 常用注解回顾Spring Boot核心配置文件分析Spring Boot四大神器使用剖析自定义Starter、自定义ActuatorSpring Boot 实现原理分析Spring Boot 数据存储配置、缓存配置、日志框架配置 DockerDocker原理分析入门介绍及安装配置网络模型常用命令讲解自己构建Docker镜像仓库管理实际案例使用搭建微服务架构Docker集群及编排工具分布式消息技术 ActiveMQActiveMQ功能简介、安装配置使用通过ActiveMQ实现简单的消息收发测试JMS的基本概念、PTP、Pub/Sub域分析JMS消息结构分析、理解JMS可靠性机制演示Topic持久化接收与非持久化接收理解Broker,Java内嵌Broker启动ActiveMQ结合Spring的使用理解ActiveMQ的传输协议ActiveMQ消息持久化方式(jdbc/KahaDB/Memory)及特点ActiveMQ集群(静态网络连接、动态网络连接)ActiveMQ的高级特性分析 KafkaKafka的基本介绍、安装配置使用Kafka的设计原理分析利用Kafka快速搭建一个消息发送接收程序Kafka数据传输的事务特点Kafka消息存储格式Kafka的集群部署配置Kafka消息分组、消息消费原理 分布式事务 分布式事务的基本介绍常用的分布式技术说明理解2PC和3PC协议分布式事务实践演练分布式环境session共享 什么时候需要用到Session共享Session共享的常用解决方案 高性能专题高性能web容器nginx Nginx简介、安装和基本配置Nginx的进程模型分析深入理解Nginx核心配置文件Nginx http模块功能配置及使用Nginx location定位功能的优先级Nginx 反向代理、负载均衡的配置Nginx 如何实现动静分离Nginx rewrite模块的使用Nginx 的使用建议Keepalived+nginx实现nginx高可用数据库性能 分库分表的基本概念mysql的主从配置分库分表的基本规则Mycat的安装使用Mycat配置深入分析及实践Mycat结合Mysql实现读写分离 MongoDBMongoDB是什么?MongoDB安装配置、及常用命令MongoDB增删改查的基本操作MongoDB查询操作命令使用深入理解MongoDB文档存储机制MongoDB索引使用分析MongoDB副本集配置及基本原理分析MongoDB分片功能介绍、分片和复制、片键的选择MongoDB数据备份、数据恢复MongoDB于spring集成使用 工作一到五年的程序员朋友面对目前的技术无从下手,感到很迷茫可以加群744677563,里面有阿里Java高级大牛直播讲解知识点,分享知识,课程内容都是各位老师多年工作经验的梳理和总结,带着大家全面、科学地建立自己的技术体系和技术认知! 我把它分为了五大专题 工程化专题 工程化专题 git git安装使用 git日常使用:fetch/pull/push/revert/rebase git分支管理git flow Jenkins多套环境(test/pre/production)系统自动化发布 Jenkins自动发布到远程主机 MavenMaven私服搭建setting.xml文件剖析pom.xml详解Maven实用插件教学(静态代码检查、生成可执行jar包)profile使用 源码分析 源码分析 Spring源码分析 Spring IOC的实现原理Spring BeanFactory源码分析Spring AOP的实现原理及配置文件详解Spring AOP的各种应用场景分析Spring MVC与Struts对比Spring HandlerMapping详解手写实现SpringMVC框架Spring与各种框架集成原理Spring JDBC操作原理基于Spring JDBC手写ORM框架 MyBatis源码分析 MyBatis3简介MyBatis3 SqlMap那些事儿数据库连接池到底是什么MyBatis3 SessionFactory实现原理MyBatis3 配置文件详解MyBatis3 事务管理与集成浅谈HibernateMyBatis3与Hibernate框架对比Netty源码分析 NIO通信原理剖析深入了解NIO缓冲区Buffer NIO Selector原理AIO编程Netty产生的背景以及基础入门 Netty高性能之道Netty的HTTP与Socket通信原理利用Netty搭建高性能的 WebSocket聊天室 Netty聊天室客户端架构实现Netty的编码解码 Netty的拆包粘包操作MsgPack原理讲解及各种序列化框架对比MsgPack与Netty整合 Netty HTTP通信与Spring整合Netty RPC架构Netty与各种架构整合以及Netty源码分析 性能调优 性能调优 JVMJVM内存模型JVM运行时数据区垃圾回收机制GC日志详解 根据GC日志调优系统,调优不靠碰运气!Mysql数据库优化 数据库底层数据结构索引数据存储结构 innodb详解SQL调优及原理分库、分表实现Nginx调优动静资源分离 nginx参数详解nginx + lua使用应用:ip过滤,扛DDOSTomcat调优 Tomcat源码、架构分析Tomcat具体调优参数设置Tomcat压力基准测试Tomcat NIO配置 双十一技术架构专题-九阳真经 九阳真经技术专题 秒杀系统实战 从0到1构建秒杀系统从all-in-one到分布式从1到100的优化重构分布式压测系统 最简单的单机压测压测应该关注的点如何做分布式压测分布式跟踪系统 如何梳理链路依赖如何做到优雅降级分布式配置系统 灵活配置热发布一切都是为了自动化分布式消息系统 异步化&系统隔离应对瞬时大流量分布式缓存体系 五级缓存体系热点数据处理 高性能及分布式专题 分布式专题分布式缓存技术 RedisRedis简介及安装Redis基础知识、数据类型、Keys的操作命令Redis对不同数据类型的操作命令使用Redis核心配置分析Redis持久化概述,RDB原理、AOP原理分析Redis事务操作分析以及发布订阅模式的操作使用Redis集群搭建,一步步教你如何搭建Redis集群Redis主从复制原理分析Redis的优化建议、最佳实践Redis的JAVA客户端使用Redis集成Spring的使用MemcachedMemcached的介绍和安装、基本配置、常用运维命令 Memcached的工作原理Memcached的常用操作命令使用分析理解Memcached的数据存储方式,理解Slab Allocator、新建Item分配内存的过程Memcached数据存储方式的缺点;分析Memcached的数据过期方式Memcached内存调优建议、常见问题的解决方案Memcached的典型应用场景、不适用的场景Memcached基于Java客户端的使用分布式接口技术 DubboDubbo的发展过程及基本原理Dubbo控制台及监控平台的搭建使用快速搭建Dubbo服务框架Dubbo常用配置的使用及分析Dubbo注册中心原理、整合Zookeeper实现服务动态发现Dubbo多注册中心、多协议支持Dubbo服务分组、服务版本号控制Dubbo并发控制、连接控制Dubbo负载均衡、集群容错使用和分析Dubbo配置文件优先级别Dubbo和DubboX的比较以及DubboX的使用介绍ThriftThrift基本介绍、 如何安装配置ThriftThrift的数据类型、基本类型、结构体类型、容器类型介绍通过Thrift搭建服务端客户端通信Thrift数据传输协议分析 Restful什么是RestfulRestful的起源如何实现一个满足Restful架构的设计Restful实战 分布式服务协调 技术 Zookeeper 什么是Zookeeper、Zookeeper的安装配置、常用命令使用Zookeeper节点特性以及节点属性分析如何搭建zookeeper的集群环境Zookeeper客户端的使用:zkclient/curatorZookeeper的实现原理分析Zookeeper实战,共享锁、 master选举Consoul Eureka 微服务架构Spring BootSpring Boot概述、如何快速搭建一个Spring Boot的项目Spring 常用注解回顾Spring Boot核心配置文件分析Spring Boot四大神器使用剖析自定义Starter、自定义ActuatorSpring Boot 实现原理分析Spring Boot 数据存储配置、缓存配置、日志框架配置 DockerDocker原理分析入门介绍及安装配置网络模型常用命令讲解自己构建Docker镜像仓库管理实际案例使用搭建微服务架构Docker集群及编排工具分布式消息技术 ActiveMQActiveMQ功能简介、安装配置使用通过ActiveMQ实现简单的消息收发测试JMS的基本概念、PTP、Pub/Sub域分析JMS消息结构分析、理解JMS可靠性机制演示Topic持久化接收与非持久化接收理解Broker,Java内嵌Broker启动ActiveMQ结合Spring的使用理解ActiveMQ的传输协议ActiveMQ消息持久化方式(jdbc/KahaDB/Memory)及特点ActiveMQ集群(静态网络连接、动态网络连接)ActiveMQ的高级特性分析 KafkaKafka的基本介绍、安装配置使用Kafka的设计原理分析利用Kafka快速搭建一个消息发送接收程序Kafka数据传输的事务特点Kafka消息存储格式Kafka的集群部署配置Kafka消息分组、消息消费原理 分布式事务 分布式事务的基本介绍常用的分布式技术说明理解2PC和3PC协议分布式事务实践演练分布式环境session共享 什么时候需要用到Session共享Session共享的常用解决方案 高性能专题高性能web容器nginx Nginx简介、安装和基本配置Nginx的进程模型分析深入理解Nginx核心配置文件Nginx http模块功能配置及使用Nginx location定位功能的优先级Nginx 反向代理、负载均衡的配置Nginx 如何实现动静分离Nginx rewrite模块的使用Nginx 的使用建议Keepalived+nginx实现nginx高可用数据库性能 分库分表的基本概念mysql的主从配置分库分表的基本规则Mycat的安装使用Mycat配置深入分析及实践Mycat结合Mysql实现读写分离 MongoDBMongoDB是什么?MongoDB安装配置、及常用命令MongoDB增删改查的基本操作MongoDB查询操作命令使用深入理解MongoDB文档存储机制MongoDB索引使用分析MongoDB副本集配置及基本原理分析MongoDB分片功能介绍、分片和复制、片键的选择MongoDB数据备份、数据恢复MongoDB于spring集成使用 工作一到五年的程序员朋友面对目前的技术无从下手,感到很迷茫可以加群744677563,里面有阿里Java高级大牛直播讲解知识点,分享知识,课程内容都是各位老师多年工作经验的梳理和总结,带着大家全面、科学地建立自己的技术体系和技术认知!

优秀的个人博客,低调大师

Kiwi,BDD行为测试框架--iOS攻城狮进阶必备技能

简介 Kiwi 是一个适用于iOS开发的行为驱动测试框架,旨在提供一个足够简单易用的BDD库. 项目主页: https://github.com/kiwi-bdd/Kiwi 示例下载: https://github.com/ios122/ios122 使用Cocopods 安装 target :AmazingAppTests, :exclusive => true do pod 'Kiwi' end 把 AmazingAppTests 改为你自己的工程中的Tests target的名字,比如我的是 iOS122Tests,然后更新即可: pod update --verbose --no-repo-update 为了快速测试Kiwi是否安装成功,你可以用下面的代码替换到你的 Tests目录下已有的文件中的默认内容,然后点击Xcode导航栏 Product->Test(或者使用快捷键 cmd + u),此时如果提示你 Test Failed,点击错误提示,会在左侧第四导航栏看到类似下面的错误: Assertions: 'Math, is pretty cool' [FAILED], expected subject to equal (KWValue) 43, got (KWValue) 42 File: MathSpec.m:9 如果不能看到上述错误信息,说明你的工程配置可能有问题,可以参考这里详细微调下: Getting Started with Kiwi 2.0 规则 Kiwi的规则由以下元素组成 #import "Kiwi.h" 导入Kiwi库.这应该在规则的文件开始处最先导入. SPEC_BEGIN(ClassName) 和 SPEC_END 宏,用于标记 KWSpec 类的开始和结束,以及测试用例的分组声明. registerMatchers(aNamespacePrefix) 注册所有使用指定命名空间前缀的匹配器.除了Kiwi默认的匹配器,这些匹配器也可以在当前规则中使用. describe(aString, aBlock) 开启一个上下文环境,可包含测试用例或嵌套其他的上下文环境. 为了使一个block中使用的变量真正被改变,它需要在定义时使用 __block 修饰符. beforeAll(aBlock) 在所有内嵌上下文或当前上下文的`itblock执行之前执行一次. afterAll(aBlock) 在所有内嵌上下文或当前上下文的`itblock执行之后执行一次. beforeEach(aBlock) 在所有包含的上下文环境的 itblock执行之前,均各执行一次.用于初始化指定上下文环境的代码,应该放在这里. afterEach(aBlock) 在所有包含的上下文环境的 itblock执行之后,均各执行一次. it(aString, aBlock) 声明一个测试用例.这里描述了对对象或行为的期望. specify(aBlock) 声明一个没有描述的测试用例.这个常用于简单的期望. pending(aString, aBlock) 可用于标记尚未完成的功能或用例,仅会使Xcode输出一个黄色警告.(有点TODO的赶脚) let(subject, aBlock) 声明一个本地工具变量,这个变量会在规则内所有上下文的每个 itblock执行前,重新初始化一次. 示例. #import "Kiwi.h" #import "YFKiwiSample.h" SPEC_BEGIN(SpecName) describe(@"ClassName", ^{ registerMatchers(@"BG"); // 注册 BGTangentMatcher, BGConvexMatcher 等. context(@"a state the component is in", ^{ let(variable, ^{ // 在每个包含的 "it" 执行前执行执行一次. return [[YFKiwiSample alloc]init]; }); beforeAll(^{ // 执行一次 NSLog(@"beforAll"); }); afterAll(^{ // Occurs once NSLog(@"afterAll"); }); beforeEach(^{ // 在每个包含的 "it" 执行前,都执行一次. NSLog(@"beforeEach"); }); afterEach(^{ // 在每个包含的 "it" 执行后,都执行一次. NSLog(@"afterEach"); }); it(@"should do something", ^{ NSLog(@"should do something"); // [[variable should] meetSomeExpectation]; }); specify(^{ NSLog(@"specify"); [[variable shouldNot] beNil]; }); context(@"inner context", ^{ NSLog(@"inner context"); it(@"does another thing", ^{ NSLog(@"does another thing"); }); pending(@"等待实现的东西", ^{ NSLog(@"等待实现的东西"); }); }); }); }); SPEC_END 期望 期望,用来验证用例中的对象行为是否符合你的语气.一个期望,具有如下形式: [[subject should] someCondition:anArgument].此处 [subject should]是表达式的类型, ... someCondition:anArgument] 是匹配器的表达式. 示例: // 可以用下面的内容替换原来的tests.m中的内容,然后cmd+u // ;测试失败可自行解决;解决不了的,继续往下看. #import "Kiwi.h" #import "YFKiwiCar.h" SPEC_BEGIN(CarSpec) describe(@"YFKiwiCar", ^{ it(@"A Car Rule", ^{ id car = [YFKiwiCar new]; [[car shouldNot] beNil]; [[car should] beKindOfClass:[YFKiwiCar class]]; [[car shouldNot] conformToProtocol:@protocol(NSCopying)]; [[[car should] have:4] wheels]; [[theValue([(YFKiwiCar *)car speed]) should] equal:theValue(42.0f)]; [[car should] receive:@selector(changeToGear:) withArguments: theValue(3)]; [car changeToGear: 3]; }); }); SPEC_END should 和 shouldNot [subject should] 和 [subject shouldNot] 表达式,类似于一个接收器,用于接收一个期望匹配器.他们后面紧跟的是真实的匹配表达式,这些表达式将真正被用于计算. 默认地,主语守卫(一种机制,可以保证nil不引起崩溃)也会在[subject should ]和 [subject shouldNot]被使用时创建.给 nil 发送消息,通常不会有任何副作用.但是,你几乎不会希望:一个表达式,只是为了给某个对象传递一个无足轻重的消息,就因为对象本身是nil.也就说,向nil对象本身发送消息,并不会有任何副作用;但是在BBD里,某个要被传递消息的对象是nil,通常是非预期行为.所以,这些表达式的对象守卫机制,会将左侧无法判定为不为nil的表达式判定为 fail失败. 标量装箱 "装箱"是固定术语译法,其实即使我们iOS常说的基本类型转NSObject类型(事实如此,勿喷). 部分表达式中,匹配器表达式的参数总是NSObject对象.当将一个标量(如int整型,float浮点型等)用于需要id类型参数的地方时,应使用theValue(一个标量)宏将标量装箱.这种机制也适用于: 当一个标量需要是一个表达式的主语(主谓宾,基本语法规则,请自行脑补)时,或者一个 存根 的值需要是一个标量时. 示例: [[theValue(1 + 1) should] equal:theValue(2)]; [[theValue(YES) shouldNot] equal:theValue(NO)]; [[theValue(20u) should] beBetween:theValue(1) and:theValue(30.0)]; YFKiwiCar * car = [YFKiwiCar new]; [[theValue(car.speed) should] beGreaterThan:theValue(40.0f)]; 消息模式 在iOS中,常将调用某个实例对象的方法成为给这个对象发送了某个消息.所以"消息模式"中的"消息",更多的指的的实例对象的方法;"消息模式"也就被用来判断对象的某个方法是否会调用以及是否会按照预期的方式调用. 一些 Kiwi 匹配器支持使用消息模式的期望.消息模式部分,常被放在一个表达式的后部,就像一个将要发给主语的消息一样. 示例: YFKiwiCar * cruiser = [[YFKiwiCar alloc]init]; [[cruiser should] receive:@selector(jumpToStarSystemWithIndex:) withArguments: theValue(3)]; [cruiser jumpToStarSystemWithIndex: 3]; 期望:数值 和 数字 [[subject shouldNot] beNil] [[subject should] beNil] [[subject should] beIdenticalTo:(id)anObject] - 比较是否完全相同 [[subject should] equal:(id)anObject] [[subject should] equal:(double)aValue withDelta:(double)aDelta] [[subject should] beWithin:(id)aDistance of:(id)aValue] [[subject should] beLessThan:(id)aValue] [[subject should] beLessThanOrEqualTo:(id)aValue] [[subject should] beGreaterThan:(id)aValue] [[subject should] beGreaterThanOrEqualTo:(id)aValue] [[subject should] beBetween:(id)aLowerEndpoint and:(id)anUpperEndpoint] [[subject should] beInTheIntervalFrom:(id)aLowerEndpoint to:(id)anUpperEndpoint] [[subject should] beTrue] [[subject should] beFalse] [[subject should] beYes] [[subject should] beNo] [[subject should] beZero] 期望: 子串匹配 [[subject should] containString:(NSString*)substring] [[subject should] containString:(NSString*)substring options:(NSStringCompareOptions)options] [[subject should] startWithString:(NSString*)prefix] [[subject should] endWithString:(NSString*)suffix] 示例: [[@"Hello, world!" should] containString:@"world"]; [[@"Hello, world!" should] containString:@"WORLD" options:NSCaseInsensitiveSearch]; [[@"Hello, world!" should] startWithString:@"Hello,"]; [[@"Hello, world!" should] endWithString:@"world!"]; 期望: 正则表达式匹配 [[subject should] matchPattern:(NSString*)pattern] [[subject should] matchPattern:(NSString*)pattern options:(NSRegularExpressionOptions)options] [[@"ababab" should] matchPattern:@"(ab)+"]; [[@" foo " shouldNot] matchPattern:@"^foo$"]; [[@"abABab" should] matchPattern:@"(ab)+" options:NSRegularExpressionCaseInsensitive]; 期望: 数量的变化 [[theBlock(^{ ... }) should] change:^{ return (NSInteger)count; }] [[theBlock(^{ ... }) should] change:^{ return (NSInteger)count; } by:+1] [[theBlock(^{ ... }) should] change:^{ return (NSInteger)count; } by:-1] 示例: it(@"Expectations: Count changes", ^{ NSMutableArray * array = [NSMutableArray arrayWithCapacity: 42]; [[theBlock(^{ [array addObject:@"foo"]; }) should] change:^{ return (NSInteger)[array count]; } by:+1]; [[theBlock(^{ [array addObject:@"bar"]; [array removeObject:@"foo"]; }) shouldNot] change:^{ return (NSInteger)[array count]; }]; [[theBlock(^{ [array removeObject:@"bar"]; }) should] change:^{ return (NSInteger)[array count]; } by:-1]; }); 期望: 对象测试 [[subject should] beKindOfClass:(Class)aClass] [[subject should] beMemberOfClass:(Class)aClass] [[subject should] conformToProtocol:(Protocol *)aProtocol] [[subject should] respondToSelector:(SEL)aSelector] 期望: 集合 对于集合主语(即,主语是集合类型的): [[subject should] beEmpty] [[subject should] contain:(id)anObject] [[subject should] containObjectsInArray:(NSArray *)anArray] [[subject should] containObjects:(id)firstObject, ...] [[subject should] haveCountOf:(NSUInteger)aCount] [[subject should] haveCountOfAtLeast:(NSUInteger)aCount] [[subject should] haveCountOfAtMost:(NSUInteger)aCount] 对于集合键(即此属性/方法名对应/返回一个集合类型的对象): [[[subject should] have:(NSUInteger)aCount] collectionKey] [[[subject should] haveAtLeast:(NSUInteger)aCount] collectionKey] [[[subject should] haveAtMost:(NSUInteger)aCount] collectionKey] 如果主语是一个集合(比如 NSArray数组), coollectionKey 可以是任何东西(比如 items),只要遵循语法结构就行.否则, coollectionKey应当是一个可以发送给主语并返回集合类型数据的消息. 更进一步说: 对于集合类型的主语,coollectionKey的数量总是根据主语的集合内的元素数量, coollectionKey 本身并无实际意义. 示例: NSArray *array = [NSArray arrayWithObject:@"foo"]; [[array should] have:1] item]; Car *car = [Car car]; [car setPassengers:[NSArray arrayWithObjects:@"Eric", "Stan", nil]]; [[[[car passengers] should] haveAtLeast:2] items]; [[[car should] haveAtLeast:2] passengers]; 期望: 交互和消息 这些期望用于验证主语是否在从创建期望到用例结束的这段时间里接收到了某个消息(或者说对象的某个方法是否被调用).这个期望会同时存储 选择器或参数等信息,并依次来决定期望是否满足. 这些期望可用于真实或模拟的独享,但是在设置 receive 表达式时,Xcode 可能会给警告(报黄). 对参数无要求的选择器: [[subject should] receive:(SEL)aSelector] [[subject should] receive:(SEL)aSelector withCount:(NSUInteger)aCount] [[subject should] receive:(SEL)aSelector withCountAtLeast:(NSUInteger)aCount] [[subject should] receive:(SEL)aSelector withCountAtMost:(NSUInteger)aCount] [[subject should] receive:(SEL)aSelector andReturn:(id)aValue] [[subject should] receive:(SEL)aSelector andReturn:(id)aValue withCount:(NSUInteger)aCount] [[subject should] receive:(SEL)aSelector andReturn:(id)aValue withCountAtLeast:(NSUInteger)aCount] [[subject should] receive:(SEL)aSelector andReturn:(id)aValue withCountAtMost:(NSUInteger)aCount] 含有指定参数的选择器: [[subject should] receive:(SEL)aSelector withArguments:(id)firstArgument, ...] [[subject should] receive:(SEL)aSelector withCount:(NSUInteger)aCount arguments:(id)firstArgument, ...] [[subject should] receive:(SEL)aSelector withCountAtLeast:(NSUInteger)aCount arguments:(id)firstArgument, ...] [[subject should] receive:(SEL)aSelector withCountAtMost:(NSUInteger)aCount arguments:(id)firstArgument, ...] [[subject should] receive:(SEL)aSelector andReturn:(id)aValue withArguments:(id)firstArgument, ...] [[subject should] receive:(SEL)aSelector andReturn:(id)aValue withCount:(NSUInteger)aCount arguments:(id)firstArgument, ...] [[subject should] receive:(SEL)aSelector andReturn:(id)aValue withCountAtLeast:(NSUInteger)aCount arguments:(id)firstArgument, ...] [[subject should] receive:(SEL)aSelector andReturn:(id)aValue withCountAtMost:(NSUInteger)aCount arguments:(id)firstArgument, ...] 示例: subject = [Cruiser cruiser]; [[subject should] receive:@selector(energyLevelInWarpCore:) andReturn:theValue(42.0f) withCount:2 arguments:theValue(7)]; [subject energyLevelInWarpCore:7]; float energyLevel = [subject energyLevelInWarpCore:7]; [[theValue(energyLevel) should] equal:theValue(42.0f)]; 注意你可以将 any()通配符用作参数.如果你只关心一个方法的部分参数的值,这回很有用: id subject = [Robot robot]; [[subject should] receive:@selector(speak:afterDelay:whenDone:) withArguments:@"Hello world",any(),any()]; [subject speak:@"Hello world" afterDelay:3 whenDone:nil]; 期望:通知 [[@"MyNotification" should] bePosted]; [[@"MyNotification" should] bePostedWithObject:(id)object]; [[@"MyNotification" should] bePostedWithUserInfo:(NSDictionary *)userInfo]; [[@"MyNotification" should] bePostedWithObject:(id)object andUserInfo:(NSDictionary *)userInfo]; [[@"MyNotification" should] bePostedEvaluatingBlock:^(NSNotification *note)block]; Example: it(@"Notification", ^{ [[@"自定义通知" should] bePosted]; NSNotification *myNotification = [NSNotification notificationWithName:@"自定义通知" object:nil]; [[NSNotificationCenter defaultCenter] postNotification:myNotification]; }); 期望: 异步调用 [[subject shouldEventually] receive:(SEL)aSelector] [[subject shouldEventually] receive:(SEL)aSelector withArguments:(id)firstArgument, ...] 期望: 异常 [[theBlock(^{ ... }) should] raise] [[theBlock(^{ ... }) should] raiseWithName:] [[theBlock(^{ ... }) should] raiseWithReason:(NSString *)aReason] [[theBlock(^{ ... }) should] raiseWithName:(NSString *)aName reason:(NSString *)aReason] 示例: [[theBlock(^{ [NSException raise:@"FooException" reason:@"Bar-ed"]; }) should] raiseWithName:@"FooException" reason:@"Bar-ed"]; 自定义匹配器 Kiwi中,自定义匹配器的最简单方式是创建KWMatcher的子类,并以适当的方式重写下面示例中的方法. 为了让你自定义的匹配器在规则中可用,你需要在规则中使用 registerMatchers(namespacePrefix)进行注册. 看下Kiwi源文件中的匹配器写法(如KWEqualMatcher等),将会使你受益匪浅. 示例: // Snippet from AnimalTypeMatcher.m #pragma mark Getting Matcher Strings // REQUIRED: Return an array of selector strings for the expectations this // matcher is used for. // // For example, this matcher handles [[subject should] beTypeOfMammal:] and // [[subject should] beTypeOfInsect:]. + (NSArray *)matcherStrings { return [NSArray arrayWithObjects:@"beTypeOfMammal:", @"beTypeOfInsect:", nil]; } #pragma mark Matching // REQUIRED: Evaluate the predicate here. // self.subject is available automatically. // self.otherSubject is a member variable you would have declared yourself. - (BOOL)evaluate { return [[self.subject animalType] isEqual:self.otherSubject]; } #pragma mark Getting Failure Messages // REQUIRED: Return a custom error message for when "should" is used. - (NSString *)failureMessageForShould { return @"expected subject to be an animal or insect"; } // OPTIONAL: If you don't override this, Kiwi uses -failureMessageForShould: and // replaces the first "to" with "not to". - (NSString *)failureMessageForShouldNot { return @"expected subject not to be an animal or insect"; } #pragma mark Configuring Matchers // These methods should correspond to the selector strings returned in +matcherStrings. // // Use them to finish configuring your matcher so that -evaluate can be called // successfully later. Being a subclass of KWMatcher handles other details like // setting up self.subject. - (void)beTypeOfMammal:(id)anObject { self.otherSubject = anObject; } - (void)beTypeOfInsect:(id)anObject { self.otherSubject = anObject; } 模拟对象 模拟对象模拟某个类,或者遵循某个写一个.他们让你在完全功能完全实现之前,就能更好地专注于对象间的交互行为,并且能降低对象间的依赖--模拟或比避免那些运行规则时几乎很难出现的情况. it(@"Mock", ^{ id carMock = [YFKiwiCar mock]; [ [carMock should] beMemberOfClass:[YFKiwiCar class]]; [ [carMock should] receive:@selector(currentGear) andReturn:theValue(3)]; [ [theValue([carMock currentGear]) should] equal:theValue(3)]; id carNullMock = [YFKiwiCar nullMock]; [ [theValue([carNullMock currentGear]) should] equal:theValue(0)]; [carNullMock applyBrakes]; id flyerMock = [KWMock mockForProtocol:@protocol(YFKiwiFlyingMachine)]; [ [flyerMock should] conformToProtocol:@protocol(YFKiwiFlyingMachine)]; [flyerMock stub:@selector(dragCoefficient) andReturn:theValue(17.0f)]; id flyerNullMock = [KWMock nullMockForProtocol:@protocol(YFKiwiFlyingMachine)]; [flyerNullMock takeOff]; }); 模拟 Null 对象 通常模拟对象收到一个非预期的选择器或消息模式时,会抛出异常(PS:iOS开发常见错误奔溃之一).在模拟对象上使用 stub 或 receive期望,期望的消息会自动添加到模拟对象上,以实现对方法的模拟. 如果你不关心模拟对象如何处理其他非预期的消息,也不想在收到非预期消息时抛出异常,那就使用 null 模拟对象吧(也即 null 对象).模拟类的实例 创建类的模拟实例(NSObject 扩展): [SomeClass mock] [SomeClass mockWithName:(NSString *)aName] [SomeClass nullMock] [SomeClass nullMockWithName:(NSString *)aName] 创建类的模拟实例: [KWMock mockForClass:(Class)aClass] [KWMock mockWithName:(NSString *)aName forClass:(Class)aClass] [KWMock nullMockForClass:(Class)aClass] [KWMock nullMockWithName:(NSString *)aName forClass:(Class)aClass] 模拟协议的实例 创建遵循某协议的实例: [KWMock mockForProtocol:(Protocol *)aProtocol] [KWMock mockWithName:(NSString *)aName forProtocol:(Protocol *)aProtocol] [KWMock nullMockForProtocol:(Protocol *)aProtocol] [KWMock nullMockWithName:(NSString *)aName forProtocol:(Protocol *)aProtocol] 存根 存根,能返回指定定选择器或消息模式的封装好的请求.Kiwi中,你可以存根真实对象(包括类对象)或模拟对象的方法.没有指定返回值的存根,将会对应返回nil,0等零值.存根需要返回标量的,标量需要使用 theValue(某个标量)宏 装箱. 所有的存根都会在规范的一个例子的末尾(一个itblock)被清除. 存根选择器: [subject stub:(SEL)aSelector] [subject stub:(SEL)aSelector andReturn:(id)aValue] 存根消息模式: [ [subject stub] *messagePattern*] [ [subject stubAndReturn:(id)aValue] *messagePattern*] 示例: id cruiser = [Cruiser cruiser]; [ [cruiser stubAndReturn:theValue(42.0f)] energyLevelInWarpCore:7]; float energyLevel = [cruiser energyLevelInWarpCore:7]; [ [theValue(energyLevel) should] equal:theValue(42.0f)]; [Cruiser stub:@selector(classification) andReturn:@"Not a moon"]; [ [ [Cruiser classification] should] equal:@"Not a moon"]; id mock = [Animal mock]; [mock stub:@selector(species) andReturn:@"P. tigris"]; [ [mock.species should] equal:@"P. tigris"]; 捕捉参数 有时,你可能想要捕捉传递给模拟对象的参数.比如,参数可能没有是一个没有很好实现 isEqual: 的对象,如果你想确认传入的参数是否是需要的,那就要单独根据某种自定义规则去验证.另外一种情况,也是最长遇到的情况,就是模拟对象接收的消息的某个参数是一个block;通常必须捕捉并执行这个block才能确认这个block的行为. 示例: id robotMock = [KWMock nullMockForClass:[YFKiwiCar class]]; KWCaptureSpy *spy = [robotMock captureArgument:@selector(speak:afterDelay:whenDone:) atIndex:2]; [[robotMock should] receive:@selector(speak:) withArguments:@"Goodbye"]; [robotMock speak:@"Hello" afterDelay:2 whenDone:^{ [robotMock speak:@"Goodbye"]; }]; void (^block)(void) = spy.argument; block(); 存根的内存管理问题 未来的某天,你或许需要存根alloc等法官法.这可能不是一个好主意,但是如果你坚持,Kiwi也是支持的.需要提前指出的是,这么做需要深入思考某些细节问题,比如如何管理初始化. Kiwi 存根遵循 Objective-C 的内存管理机制.当存根将返回值写入一个对象时,如果选择器是以alloc,或new开头,或含有 copy时,retain消息将会由存根自动在对象发送前发送. 因此,调用者不需要特别处理由存根返回的对象的内存管理问题.警告 Kiwi深度依赖Objective-C的运行时机制,包括消息转发(比如 forwardInvocation:).因为Kiwi需要预先判断出来哪些方法可以安全调用.使用Kiwi时,有一些惯例,也是你需要遵守的. 为了使情况简化和有条理,某些方法/选择器,是决不能在消息模式中使用,接收期望,或者被存根;否则它们的常规行为将会被改变.不支持使用这些控制器,而且使用后的代码的行为结果也会变的很奇怪. 在实践中,对于高质量的程序代码,你可能不需要担心这些,但是最好还是对这些有些印象. 黑名单(使用有风险): 所有不在白名单中的NSObject类方法和NSObject协议中的方法.(比如-class, -superclass, -retain, -release等.) 所有的Kiwi对象和方法. 白名单(可安全使用): +alloc +new +copy -copy -mutableCopy -isEqual: -description -hash -init 其他任何不在NSObject类或NSobject协议中的方法. 异步测试 iOS应用经常有组件需要在后台和主线程中内容沟通.为此,Kiwi支持异步测试;因此就可以进行集成测试-一起测试多个对象.expectFutureValue() 和 shouldEventually 为了设置异步测试,你 必须 使用 expectFutureValue 装箱,并且使用 shouldEventually 或 shouldEventuallyBeforeTimingOutAfter来验证. shouldEventually 默认在判定为失败前等待一秒. [[expectFutureValue(myObject) shouldEventually] beNonNil]; 标量的处理 当主语中含有标量时,应该使用 expectFutureValue中使用 theValue装箱标量.例如: [[expectFutureValue(theValue(myBool)) shouldEventually] beYes]; shouldEventuallyBeforeTimingOutAfter() 这个block默认值是2秒而不是1秒. [[expectFutureValue(fetchedData) shouldEventuallyBeforeTimingOutAfter(2.0)] equal:@"expected response data"]; 反转 也有shouldNotEventually和 shouldNotEventuallyBeforeTimingOutAfter 的变体.一个基于LRResty的示例: 这个block会在匹配器满足或者超时(默认: 1秒)时完成. This will block until the matcher is satisfied or it times out (default: 1s) context(@"Fetching service data", ^{ it(@"should receive data within one second", ^{ __block NSString *fetchedData = nil; [[LRResty client] get:@"http://www.example.com" withBlock:^(LRRestyResponse* r) { NSLog(@"That's it! %@", [r asString]); fetchedData = [r asString]; }]; [[expectFutureValue(fetchedData) shouldEventually] beNonNil]; }); }); 文章转载自 开源中国社区[https://www.oschina.net]

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册