-
为什么要做测试(必答题)
测试不能提高代码质量;
自己写的代码自己一般检查不出来BUG;
流程中复杂的数据结构,第三方模块和方法,内部细节不够详细(类似于黑盒),必须要测试才能发现潜在的bug;
bug可能因为两块正常的代码耦合在一起而产生,所以通畅地测试整个流程很有必要; -
web开发的测试包括哪些
宏观的测试内容包括:
用户感受:页面样式正确、文案通顺、交互正常、流程逻辑合理;
运营需求:流程(样式、文案、引导等等)和目的一致,数据能随时统计;
开发人员:代码重用率、需求扩展是否容易、文档是否齐全; -
测试需要遵循哪些规则
-
有哪些常规的测试方法
代码评审
黑盒测试 -
有哪些测试工具,自动化测试工具
Chrome DevTools
postman(模拟请求)
fiddler(抓包)
Selenium(模拟访问 & 结果存储分析) -
如何编写测试文档(范例)
用例:
描述:
前置条件:
后置条件:
预期结果:
是否通过:
附链接参考:http://www.ibm.com/support/knowledgecenter/zh/SSYMRC_4.0.0/com.ibm.rational.test.qm.doc/topics/t_testcase_template_ref.html -
在以往项目开发过程中,测试的时候遇到的问题(必答题)
调用api错误(调了别人的分支、调用了测试分支);
数据解析错误(传参不一样数据结构不一样的接口);
sql错误:表缺少字段,或者存储了一个null到非null字段;
spl_autoload_register 入门级讲解
标签(空格分隔): PHP自动加载
__autoload()
了解 spl_autoload_register() 前先了解一下 __autoload(),或者直接 跳过此节:
function __autoload($c)
{
var_dump($c);
}
new Say();
//输出
string(3) "Say"
Fatal error: Class 'Say' not found in E:\Dropbox\liubole\htdocs\test\index.php on line xx
当Say类不存在的时候,自动执行了 __autoload() 函数,并将类名当做参数传入了 __autoload()。
如果在 __autoload() 里面 include 一下包含 Say 类的文件,可以“亡羊补牢”,代码继续正常执行:
function __autoload($c)
{
echo "not fund class \"{$c}\", auto loading...\n";
//libs下的say.php,大小写无影响
include_once __DIR__ . "/libs/{$c}.php";
}
$say = new Say();
$say->hello();
包含 Say 类(libs/say.php)的文件内容:
class Say
{
function __construct()
{
echo "init class \"Say\"...\n";
}
function hello()
{
echo "say: hello~\n";
}
}
再次执行,结果如下:
not fund class "Say", auto loading...
init class "Say"...
say: hello~
至此我们大概了解了 __autoload() 的实际执行情况。
spl_autoload_register()
PS:前缀 SPL 的全称是 Standard PHP Library。
当PHP找不到类文件会调用 __autoload() 方法,当注册了自己的函数或方法时,PHP不会调用 __autoload() 函数,而会调用自定义的函数。
如果在你的程序中已经实现了__autoload函数,它必须显式注册到__autoload栈中。因为spl_autoload_register()函数会将Zend Engine中的__autoload函数取代为spl_autoload()或spl_autoload_call()。
上面的话来自 php.net。OK,那怎样算 显式注册到__autoload栈中 呢?
◔ ‸◔ ... ◔ ‸◔ ..... ◔ ‸◔ ...... ◔ ‸◔ ........◔ ‸◔
答案是:spl_autoload_register("__autoload") 。以下是我找来的一点摘要,描述了为何需要这样做:
-
autoload机制的主要执行过程为:
(1) 检查执行器全局变量函数指针autoload_func是否为NULL。
(2) 如果autoload_func==NULL,则查找系统中是否定义有__autoload()函数,如果没有,则报告错误并退出。
(3) 如果定义了__autoload()函数,则执行__autoload()尝试加载类,并返回加载结果。
(4) 如果autoload_func不为NULL,则直接执行autoload_func指针指向的函数用来加载类。注意此时并不检查__autoload()函数是否定义。 -
如果既实现了__autoload()函数,又实现了autoload_func(将autoload_func指向某一PHP函数),那么只执行autoload_func函数。
-
SPL autoload机制的实现是通过将函数指针autoload_func指向自己实现的具有自动装载功能的函数来实现的。SPL有两个不同的函数spl_autoload, spl_autoload_call,通过将autoload_func指向这两个不同的函数地址来实现不同的自动加载机制。spl_autoload的功能比较简单,而且它是在SPL扩展中实现的,我们无法扩充它的功能。所以我们需要spl_autoload_register将autoload_func指向spl_autoload_call的这个功能。
-
在SPL模块内部,有一个全局变量autoload_functions,它本质上是一个HashTable,不过我们可以将其简单的看作一个链表,链表中的每一个元素都是一个函数指针,指向一个具有自动加载类功能的函数。spl_autoload_call按顺序执行这个链表中每个函数,在每个函数执行完成后都判断一次需要的类是否已经加载,如果加载成功就直接返回,不再继续执行链表中的其它函数。
-
自动加载函数链表autoload_functions由spl_autoload_register函数维护,它可以将用户定义的自动加载函数注册到这个链表中,并将autoload_func函数指针指向spl_autoload_call函数。
欲知详情请移步 __autoload机制详解以及与spl_autoload_register的区别, 文章把原因讲的很详细,时间匆忙我仅囫囵吞枣瞄了眼。
本文涉及资料:
PHP: spl_autoload_register - Manual
__autoload机制详解以及与spl_autoload_register的区别
判断点是否在矩形内的算法
声明:想获取更多更详细的知识请移步:计算几何算法概览
需求描述:
有一个 4*4 的方格,用户可以选择起点和落点形成矩形,每次形成的矩形不能重叠,起点可以和落点坐标一样。现已知起点、未选点集合和已选点集合,求落点的集合。
算法重点:转换为判断点P是否在一个矩形内。
方法一:建立平面直角坐标系,根据横坐标和纵坐标判断点是否在矩形内。
/**
* 判断点是否在矩形内(含边)。p1、p2是矩形的两个对角点。
* 该算法和平面直角坐标系怎么建立无关
* @param $rc
* @param $p
* @return bool
*/
function isInRect($rc, $p)
{
$xi = ($p->x - $rc->p1->x) * ($p->x - $rc->p2->x);
$yi = ($p->y - $rc->p1->y) * ($p->y - $rc->p2->y);
return ($xi <= 0) && ($yi <= 0);
}
方法二:假设矩形左、右、上、下分别在直线 L1、L2、L3、L4 上,只需要证明点 P 同时在上下、左右直线之间即可。
过P作一条直线相交于 L1、L2 的 C1 、C2 ,向量PC1 、PC2 同向则P在 L1、L2 之外,反向则在 L1、L2 之内。同理可判断P是否在 L3、L4 之间。
$$ PC_1 = (C_1x - P_x , C_1y - P_y); PC_2 = (C_2x - P_x , C_2y - P_y);$$
$$ \frac{C_2x - P_x}{C_1x - P_x } > 0 或 \frac{C_2y - P_y}{C_1y - P_y } > 0 => PC_1、PC_2 同向 $$
$$(C_1x-P_x)^2 + (C_1y-P_y)^2 = 0 => PC_1为零向量 => PC_1、PC_2 同向$$
$$(C_2x-P_x)^2 + (C_2y-P_y)^2 = 0 => PC_2为零向量 => PC_1、PC_2 同向$$
如果:
$$ L_1: y = mx + t_1; $$
$$ L_2: y = mx + t_2; $$
$$ C_1C_2: y = (m+1)x; $$
$$ P_1:(P_1x, P_1y); P_2:(P_3x, P_3y) 是L_1上的两个矩形顶点;$$
$$ P_3:(P_3x, P_3y); P_4:(P_4x, P_4y) 是L_2上的两个矩形顶点;$$
则:
$$ 若点P和{P_1,P_2,P_3,P_4}中的某一点重合,则P在矩形内; $$
$$ 若 (t_2 - P_x)*(t_1 - P_x)>0, t_1=\frac{P_1yP_2x-P_1xP_2y}{P_2x-P_1x},t_2=\frac{P_3yP_4x-P_3xP_4y}{P_4x-P_3x} 则P在矩形内;$$
$$ 若 (t_2 - P_x)*(t_1 - P_x)>0, t_1=\frac{P_1yP_2x-P_1xP_2y}{P_2x-P_1x},t_2=\frac{P_3yP_4x-P_3xP_4y}{P_4x-P_3x} 则P在矩形内;$$
/**
* 此处省略n行代码
*/
方法三:以点P为起点,向左作射线,如果射线和矩形的焦点个数是奇数,则P在矩形内。
PS:对以下特例不做考虑:1、对于矩形的水平边不作考虑;2、对于矩形的顶点和L相交的情况。
/**
* 此处省略m行代码
*/
总结:方法一简单实用,方法三可以推广到判断点是否在任意多边形内。
RabbitMQ在项目中的使用方式+简略介绍
声明:文章写于2016-11-10,项目中RabbitMQ的使用方式在后面可能会发生变化。
消息队列(MessageQueue)的好处:
- 简化业务流程
- 异步处理消息
- 减少组件耦合
- 消减数据峰值
消息队列的应用场景:
- 场景1:
支付成功以后,给用户发送优惠券or红包; -
场景2:
用户注册成功后发送通知邮件or短信; -
场景3:
活动里面的站内信发送;
. . . . . .
总结起来就是,把单个业务简洁化,通过消息系统把不相关的业务连接起来,实现更复杂的业务链,降低整个系统的复杂度。
安装
安装方法自行google,如果要在生产环境中安装请先联系运维,涉及到安全问题。
项目中使用
//发布消息
use CI123\Shop\Tool\RabbitMq\Publisher;
Publisher::publish('routing_key', $message);
//监听消息
use Api\Tool\RabbitMq\Subscriber;
Subscriber::subscribe('routing_key', function ($msgObj) {
$message = $msgObj->body;
Subscriber::ack($msgObj);
});
以上是对RabbitMQ极其简陋的介绍,下面是重点:
一、我们项目中怎么设置RabbitMQ的:
1. 持久化
真正的持久保存消息,需要设置三个地方:
1.1 将交换机设置成durable;
1.2 将队列设置成durable;
1.3 将消息的delivery_mode设置成2;
PS:
缺少任何一个设置,都无法真正持久化;
持久化会消耗额外的性能;
2. 自动删除
2.1 消息在队列里存在时间是5分钟;
2.2 队列本身存在时间是30分钟;
2.3 过期的消息会被移出到“死信队列”;
2.4 死信队列里的消息存在7天;
2.5 死信队列本身存在10天;
3. 消费者应答机制(ack/nack机制)
3.1 如果消息已经被正确消费了,你可以通过ack方法告诉rabbitmq,rabbitmq会删除这条消息;
3.2 反之,你可以调用nack告诉RabbitMQ消费失败,rabbitmq会立马将消息移到“死信队列”;
3.3 或者什么都不做,等待消息过期后被移动到“死信队列”。
4. 生产者确认机制(Confirm机制)
4.1 发布消息后,发送者可以通过异步的回调知道消息是否发送成功;
PS:目前项目中基本没有使用。
二、初步理解RabbitMQ
1. 四个比较重要的概念
- 虚拟主机(vhost):每个vhost都有单独一套交换器、队列、绑定规则
- 交换器(exchange):将消息发送到队列
常见相关名词:
+ 名称(exchange):交换器名字,不能重复
+ 类型(type):fanout/direct/topic/headers
+ 持久化(durable):服务重启后是否要恢复
+ 自动删除(auto-delete):没有队列使用交换器时自动删除 - 队列(queue):存取消息
常见相关名词:
+ 名称(queue):队列名字,不能重复
+ 持久化(durable):服务重启后是否要恢复
+ 自动删除(auto-delete):没有消费者时自动删除
+ 声明(arguments): 消息存在时间(x-message-ttl)、队列存在时间(x-expires) - 绑定(binding):将队列和交换机绑定,以通过routing_key来收发消息;
2. 四种Exchange
- fanout :广播
- direct:routing_key全部匹配
- topic:可以理解为在direct的基础上给routing_key增加了正则匹配,当然肯定不是正则;
- headers:消息里包含headers,如果消息的headers和队列能匹配上就将消息投递到该headers,可以指定是完全匹配(all)还是只需要匹配单个(one);
3. 其他概念和结论
- 消息的获取有主动拉取、被动接收,发布/订阅模式是被动接收;
- 交换器、队列的属性一旦声明就不可更改,但是绑定是可以解绑的(unbind);
- 当一个 message 被路由到多个 queue 中时,在某个 queue 中的某个 message 的“死亡”不会对相同 message 在其他 queue 中的生存状况;
- 同时设置了队列生存时间T1和消息生存时间T2后,消息在队列中的存在时间是T1/T2之中的最小值;
- 如果没有设置死信队列,即便你设置了durable,消息到期后也会被丢弃;
4.目前的使用状况
- 按照普通的消息队列来使用:佣金、理财、保险;
- 改造为延时队列:微信模板消息,通过延时队列来重发上次发送失败的消息;
- 可以改造为任务队列使用:待定;
禁止用户滑动屏幕的js设置
整 理:晋 哲 时 间:2017-01-06 说 明:弹出框弹出后,当用户滑动屏幕时主体页面应该设置为不能滚动的
一、移动端
禁止滚动设置
$('body').bind('touchmove',function(){return false;});
恢复滚动设置
$('body').unbind('touchmove');
二、PC端
禁止滚动设置
$('body').css({overflow:"hidden"});
恢复滚动设置
$('body').css({overflow:"auto"});
深入理解CI框架:框架如何运转起来(原理篇)
标签(空格分隔): codeigniter 源码
一、核心
index.php:入口文件
入口文件主要定义路径和环境,包括BASEPATH
、FCPATH
、APPPATH
等常量表示的路径和application
、system
等变量表示的路径;
index.php
通过$_SERVER['CI_ENV']
来分辨当前的执行环境,目前支持development
、testing
、production
三种(默认development
),如果你在apache
里配置CI_ENV
为其他值(比如mytest),项目会输出错误信息并停止执行;
core/CodeIgniter.php:分派请求
CodeIgniter.php
加载了各种各样的类和方法,它解析用户的请求(URI类
),判断请求对应哪个控制器的哪个方法(Router类
),创建控制器对象,执行方法,得到结果并输出(Output类
),期间还会加载并执行预先设置好的各种钩子(Hooks类
)。
- 很显然,
CodeIgniter.php
是CI框架最重要的文件之一,它根据用户请求调用不同类处理并输出结果。- CI框架解析用户请求时,主要依据
$_SERVER
这个变量($_SERVER['REQUEST_METHOD']
)- 除了各种类,
CodeIgniter.php
还加载了很多公共方法,详见core/Common.php
和core/compat
二、提取核心代码
$RTR =& load_class('Router', 'core');
$class = ucfirst($RTR->class);
require_once BASEPATH.'core/Controller.php';
$CI = new $class();
三、细枝末节
core/Common.php:公共方法
Common Functions
——这是Common.php
定位,用来存放各种公共方法,比如is_php
、is_https
、is_cli
、show_404
、function_usable
等等。
core/compat
目录下也存放有很多公共方法文件,比如hash.php
、mbstring.php
、password.php
、standard.php
,我们经常用的array_column
函数就是在standard.php
里定义的。
四、黑科技
extract:从数组中将变量导入到当前的符号表
//index.php
$vars = array('name' => 'jane', 'age' => 20);
extract($vars);
var_dump($name);
ob_xxx函数:把输出放到缓冲区、从缓冲区获取内容
ob_start:打开输出控制缓冲
ob_end_flush:冲刷出(送出)输出缓冲区内容并关闭缓冲
ob_end_clean:清空(擦除)缓冲区并关闭输出缓冲
//index2.php
//将一个php文件保存到文本文件里
//load->view 如何实现的
ob_start();
include('./index.php');
$buffer = ob_get_contents();
file_put_contents('index.php.txt', $buffer);
@ob_end_clean();
ob_end_flush();
ReflectionMethod:反射类
//检查方法是否可以访问
//如何在访问一个private方法或不存在的方法的时候显示404
$reflection = new ReflectionMethod($class, $method);
if ( ! $reflection->isPublic() OR $reflection->isConstructor())
{
$e404 = TRUE;
}
弹性布局Flexbox
整 理:晋 哲 时 间:2017-01-06 说 明:使用弹性布局,设置自适应布局、水平垂直居中等样式
弹性布局很早就出来了,但之前一直没有使用Flexbox主要还是考虑兼容性,且兼容性写法太冗余。目前,旧版的属性都已被重新修改,所有浏览器的兼容性也得到很大支持,移动端可以放心使用了,当然浏览器前缀还是要加上的。
能够被运用到的布局结构,如响应式布局、选项卡分布、水平垂直居中等,优先考虑使用flex,代替之前一些特殊布局所使用的传统解决方案:使用display属性或position属性或float属性。
flex常用属性
display:将元素设置为内联或者块flexbox容器元素 flex:复合属性。设置或检索弹性盒模型对象的子元素如何分配空间。 justify-content:控制flex容器内item的水平对齐方式 align-items:控制flex容器内item的垂直对齐方式 flex-direction:定义弹性盒子元素的排列方向
其属性值:
display:flex | inline-flex flex:none | < flex-grow > | < flex-shrink > | < flex-basis > justify-content:flex-start | flex-end | center | space-between | space-around align-items: flex-start | flex-end | center | baseline | stretch flex-direction:row | row-reverse | column | column-reverse
常见用法:
一、自适应布局
<!-- HTML --> <div class="box"> <div class="subnav"></div> <div class="content"></div> </div> <!-- CSS --> .box{ display: -webkit-box; display: -webkit-flex; display: flex; } .subnav{ width: 100px; height: 100px; background: #000; } .content{ background: #f60; -webkit-box-flex: 1; -webkit-flex: 1; flex: 1; } 解释:subnav固定宽度,content自适应撑满剩余空间
二、使用 Flexbox 的水平垂直居中布局
<!-- HTML --> <div class="box"> <div class="content"></div> </div> <!-- CSS --> .box{ height: 200px; background: #eee; display: -webkit-box; display: -webkit-flex; display: flex; -webkit-align-items: center; align-items: center; -webkit-justify-content: center; justify-content: center; } .content{ width: 100px; height: 100px; background: #f60; }
Sass mixin制作水平垂直居中
整 理:晋 哲 时 间:2017-01-06 说 明:使用Sass mixin 的方式将一些固定样式用法封装起来,能够提高样式复用性。
如水平垂直居中的示例:
常用的水平垂直居中方式是使用CSS transform属性或负向margin的方式,将这两种方法集成起来,通过判断是否有宽高值生成对应的样式。
/*定义mixin*/ @mixin center($width: null, $height: null){ position: absolute; top: 50%; left: 50%; @if not $width and not $height{ transform: translate(-50%, -50%); } @else if $width and $height{ width: $width; height: $height; margin: -($height/2) #{0 0} -($width/2); } @else if not $height{ width: $width; margin-left: -($width/2); transform: translate(0, -50%); } @else{ height: $height; margin-top: -($height/2); transform: translate(-50%, 0); } } /*使用*/ .box1{ @include center; } .box2{ @include center(200px); } .box3{ @include center(null, 300px); } .box4{ @include center(200px, 300px); }
CSS3 Animation steps()函数属性运用
整 理:晋 哲 时 间:2017-01-06 说 明:利用steps()函数的特效可以制作类gif动画的动态效果,搭配sprite图使用。
作用优势:代替gif动图的繁琐制作,调整动画便捷,较gif更清晰,图片进行压缩,文件可更小。
使用说明:animation的steps()函数内部的第一个参数设置为正整数,可以计算等分@keyframes规则内from到to的差值,最终进行动画切换。
如示例代码:
.dragon{ width: 178px; height: 188px; background: url(images/dragon.png) 0 0 no-repeat; animation: run_dragon 2s steps(10) infinite; } @keyframes run_dragon{ 0% {background-position: 0 0;} 100%{background-position: -1780px 0;} }
IOS键盘弹出导致fixed定位错位
整 理:晋 哲 时 间:2017-01-06 说 明:主要是input的焦点事件触发弹出键盘导致fixed定位失效,安卓没有该bug
方法一:使用absolute模拟fixed
1、当触发事件时,将固定定位的元素设置为【position:absolute;top:0;】
若页面高度一屏之内,则事件结束后还原样式即可;若页面很长则继续下一步
2、当页面滚动时,添加滚动事件,更新top值,使其看似一直在顶部
window.onscroll=function(){ $(".fixed").css("top",$(window).scrollTop()); }
缺点:因为top值时实时计算的,页面滚动时,会有缓冲时间。
方法二:将溢出内容部分设置内部滚动,设置样式【…height:100vh;overflow:auto;】
因为键盘弹出时fixed失效,元素的位置自动根据scrollTop滚动的距离去定位,所以当页面高度为屏幕高度时,系统自动的定位也就在顶部了。
优点:该方法不需要再去设置复杂的js,仅设置样式即可,也是最常用的方法。
缺点:正常情况下是没有问题的,需注意的是商品列表中商品图片使用了lazyload.js,当内容部分设置overflow后,懒加载就失效了,因为它默认是整段body进行滚动监听的。解决方法:lazyload.js有参数【container】可以设置内部框架懒加载的。