单元测试并不能证明你的代码是正确的,只能证明你的代码是没有错误的。
Keep bar green and keep your code cool
第一种方式<4.0的JUnit版本
1、 在已经编写好的项目中新建一个package用于单元测试。
2、 要在buildpath中加入JUnit对应的包。
3、 新建一个类,比如unitTest
4、 当前的类需要继承
Test类,需要导入一下的一些包:
import static org.junit.Assert.*;
import junit.framework.TestCase;
import org.junit.Test;
5、 编写自己的测试函数,可以编写多个,感觉上每个函数都相当于一个main方法,要注意的是需要用来执行的函数都要以test开头。
6、 在对应的测试类上点击Run as 之后点击JUnit Test 就可以执行对应的test开头的方法了。
第二种方式>=4.0的JUnit版本
1、 这种方式是基于注解来进行的,先要加上对应的包import org.junit.Test,其他的就不用加了。
2、 类名不需要继承TestCase,测试方法也不需要以test开头。
3、 只需要在方法的前面加上@Test的注解,之后 Run as—>JUnit test这样就会自动对加了注解的方法进行测试。
使用注解的方式还是比较推荐的,最好在利用注解的时候方法名也能与之前的保持一致,这样就能与4.0版本之前的JUnit兼容了。
这种方式的大致原理还是利用反射,先获得Class类实例,之后利用getMethods方法得到这个类的所有的方法,之后遍历这个方法,判断每个方法是否加上了@Test注解,如果加上了注解,就执行。大多数框架内部都是依靠反射来进行的。实际情况中还是比较推荐使用注解的,还有一些常用的注解,比如:@Before @After这两个分别表示方法(@Test之后的)执行之前要执行的部分,以及方法执行之后要执行的部分,注意这里每个被@Test标注过的方法在执行之前与执行之后都要执行@Before以及@After标注过的方法,因此被这两个注解标记过的方法可能会执行多次。
对于@BeforeClass以及@AfterClass顾名思义就表示在整个测试类执行之前与执行之后要执行的方法,被这两个注解标记过的方法在整个类的测试过程中只是执行一次。
还有一个常用到的方法是Assert.assertEquals方法,表示预期的结果是否与实际出现的结果是否一致,可以有三个参数,第一个参数表示不一致时候的报错信息,第二个参数表示期望的结果,第三个参数表示实际的结果。
还有一部分是关于组合模式的使用,比如写了好多的测试类,ATest BTest ....总不能一个一个点,能一起让这些测试类都运行起来就是最好不过了,这时候要使用到两个注解:@RunWith(Suite.class)以及@SuiteClasses({ xxTest.class,xxTest.class })
当然JUnit的整个过程中还涉及到了许多经典的设计模式,这个再进一步进行分析。
下面是一个实际的例子,展示一下常见的几个注解的使用:
//一个简单的Student类以及一个Teacher类 输出其基本信息 package com.test.unittest; public class Student { int id; int age; String name; public Student(int id, int age, String name) { super(); this.id = id; this.age = age; this.name = name; } public int getId() { return id; } public void setId(int id) { this.id = id; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } |
public String getName() { return name; } public void setName(String name) { this.name = name; } public void info() { System.out.println("the stu info:"+this.age+" "+this.id+" "+this.name); } } package com.test.unittest; public class Teacher { String tname; String tage; public Teacher(String tname, String tage) { super(); this.tname = tname; this.tage = tage; } public String getTname() { return tname; } public void setTname(String tname) { this.tname = tname; } public String getTage() { return tage; } public void setTage(String tage) { this.tage = tage; } public void info(){ System.out.println("the teacher info:"+this.tage+" " +this.tname); } } |
后面这部分就是对两个类进行的单元测试以及一个组合方式的使用
package com.Unittest; import org.junit.After; import org.junit.Before; import org.junit.Test; import com.test.unittest.Student; public class StudentTest { Student stu=new Student(1,23,"令狐冲"); @Before public void setUp(){ System.out.println("Student Initial"); } @Test public void infoTest() { stu.info(); } @After public void tearDown(){ System.out.println("Student Destroy"); } } package com.Unittest; import org.junit.After; import org.junit.Before; import org.junit.Test; import com.test.unittest.Teacher; public class TeacherTest { Teacher teacher=new Teacher("风清扬","90"); @Before public void setUp(){ System.out.println("Teacher Initial"); } @Test public void infoTest() { teacher.info(); } @After public void tearDown(){ System.out.println("Teacher Destroy"); } } package com.Unittest; import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; import com.test.unittest.Student; @RunWith(Suite.class) @SuiteClasses({StudentTest.class,TeacherTest.class}) public class AllTest { } /*输出的结果如下: Student Initial the stu info:23 1 令狐冲 Student Destroy Teacher Initial the teacher info:90 风清扬 Teacher Destroy */ |
补充说明:
写作业的时候把测试类一个一个手敲进去,真是太out了,还是用eclipse中自带的生成JUnit test的类比较好一点,直接在测试的那个package下面,创建一个新的JUnit Test Class 选定版本以及选定class under test 这个表示你希望生成哪一个类的测试类,这样生成的测试类中命名也比较规范,比如相同的方法名不同参数的方法,连参数类型都写上去了,比以前直接用a b c d...1 2 3 4....来区别同名的方法正规多了....
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
感觉有必要把iOS开发中的手势识别做一个小小的总结。在上一篇iOS开发之自定义表情键盘(组件封装与自动布局)博客中用到了一个轻击手势,就是在轻击TextView时从表情键盘回到系统键盘,在TextView中的手是用storyboard添加的。下面会先给出如何用storyboard给相应的控件添加手势,然后在用纯代码的方式给我们的控件添加手势,手势的用法比较简单。和button的用法类似,也是目标动作回调,话不多说,切入今天的正题。总共有六种手势识别:轻击手势(TapGestureRecognizer),轻扫手势(SwipeGestureRecognizer), 长按手势(LongPressGestureRecognizer), 拖动手势(PanGestureRecognizer), 捏合手势(PinchGestureRecognizer),旋转手势(RotationGestureRecognizer);
其实这些手势用touche事件完全可以实现,
苹果就是把常用的触摸事件封装成手势,来提供给用户。读者完全可以用TouchesMoved来写拖动手势等
一,用storyboard给控件添加手势识别,当然啦用storyboard得截张图啦
1.用storyboard添加手势识别,和添加一个Button的步骤一样,首先我们得找到相应的手势,把手势识别的控件拖到我们要添加手势的控件中,截图如下:
2.给我们拖出的手势添加回调事件,和给Button回调事件没啥区别的,在回调方法中添加要实现的业务逻辑即可,截图如下:
二,纯代码添加手势识别
用storyboard可以大大简化我们的操作,不过纯代码的方式还是要会的,就像要Dreamwear编辑网页一样(当然啦,storyboard的拖拽功能要比Dreamwear的拖拽强大的多),用纯代码敲出来的更为灵活,更加便于维护。不过用storyboard可以减少我们的
工作量,这两个要配合着使用才能大大的提高我们的开发效率。个人感觉用storyboard把框架搭起来(Controller间的关系),一下小的东西还是用纯代码敲出来更好一些。下面就给出如何给我们的控件用纯代码的方式来添加手势识别。
1.轻击手势(TapGestureRecognizer)的添加
初始化代码TapGestureRecongnizer的代码如下:
1 //新建tap手势
2 UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(tapGesture:)];
3 //设置点击次数和点击手指数
4 tapGesture.numberOfTapsRequired = 1; //点击次数
5 tapGesture.numberOfTouchesRequired = 1; //点击手指数
6 [self.view addGestureRecognizer:tapGesture];
在回调方法中添加相应的业务逻辑:
1 //轻击手势触发方法
2 -(void)tapGesture:(id)sender
3 {
4 //轻击后要做的事情
5 }
2.长按手势(LongPressGestureRecognizer)
初始化代码:
1 //添加长摁手势
2 UILongPressGestureRecognizer *longPressGesture = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPressGesture:)];
3 //设置长按时间
4 longPressGesture.minimumPressDuration = 0.5; //(2秒)
5 [self.view addGestureRecognizer:longPressGesture];
在对应的回调方法中添加相应的方法(当手势开始时执行):
1 //常摁手势触发方法 2 -(void)longPressGesture:(id)sender 3 { 4 UILongPressGestureRecognizer *longPress = sender; 5 if (longPress.state == UIGestureRecognizerStateBegan) 6 { 7 UIAlertView *alter = [[UIAlertView alloc] initWithTitle:@"提示" message:@"长按触发" delegate:nil cancelButtonTitle:@"取消" otherButtonTitles: nil]; 8 [alter show]; 9 } 10 } |
代码说明:手势的常用状态如下
开始:UIGestureRecognizerStateBegan
改变:UIGestureRecognizerStateChanged
结束:UIGestureRecognizerStateEnded
取消:UIGestureRecognizerStateCancelled
失败:UIGestureRecognizerStateFailed
3.轻扫手势(SwipeGestureRecognizer)
在初始化轻扫手势的时候得指定轻扫的方向,上下左右。 如果要要添加多个轻扫方向,就得添加多个轻扫手势,不过回调的是同一个方法。
添加轻扫手势,一个向左一个向右,代码如下:
1 //添加轻扫手势
2 UISwipeGestureRecognizer *swipeGesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGesture:)];
3 //设置轻扫的方向
4 swipeGesture.direction = UISwipeGestureRecognizerDirectionRight; //默认向右
5 [self.view addGestureRecognizer:swipeGesture];
6
7 //添加轻扫手势
8 UISwipeGestureRecognizer *swipeGestureLeft = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swipeGesture:)];
9 //设置轻扫的方向
10 swipeGestureLeft.direction = UISwipeGestureRecognizerDirectionLeft; //默认向右
11 [self.view addGestureRecognizer:swipeGestureLeft];
回调方法如下:
1 //轻扫手势触发方法 2 -(void)swipeGesture:(id)sender 3 { 4 UISwipeGestureRecognizer *swipe = sender; 5 if (swipe.direction == UISwipeGestureRecognizerDirectionLeft) 6 { 7 //向左轻扫做的事情 8 } 9 if (swipe.direction == UISwipeGestureRecognizerDirectionRight) 10 { 11 //向右轻扫做的事情 12 } 13 } 14 |
4.捏合手势(PinchGestureRecognizer)
捏合手势初始化
1 //添加捏合手势
2 UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinchGesture:)];
3 [self.view addGestureRecognizer:pinchGesture];
捏合手势要触发的方法(放大或者缩小图片):
1 ////捏合手势触发方法 2 -(void) pinchGesture:(id)sender 3 { 4 UIPinchGestureRecognizer *gesture = sender; 5 6 //手势改变时 7 if (gesture.state == UIGestureRecognizerStateChanged) 8 { 9 //捏合手势中scale属性记录的缩放比例 10 _imageView.transform = CGAffineTransformMakeScale(gesture.scale, gesture.scale); 11 } 12 13 //结束后恢复 14 if(gesture.state==UIGestureRecognizerStateEnded) 15 { 16 [UIView animateWithDuration:0.5 animations:^{ 17 _imageView.transform = CGAffineTransformIdentity;//取消一切形变 18 }]; 19 } 20 } |
5.拖动手势(PanGestureRecognizer)
拖动手势的初始化
1 //添加拖动手势
2 UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panGesture:)];
3 [self.view addGestureRecognizer:panGesture];
拖动手势要做的方法(通过translationInView获取移动的点,和TouchesMoved方法类似)
1 //拖动手势
2 -(void) panGesture:(id)sender
3 {
4 UIPanGestureRecognizer *panGesture = sender;
5
6 CGPoint movePoint = [panGesture translationInView:self.view];
7
8 //做你想做的事儿
9 }
6.旋转手势(RotationGestureRecognizer)
旋转手势的初始化
1 //添加旋转手势
2 UIRotationGestureRecognizer *rotationGesture = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(rotationGesture:)];
3 [self.view addGestureRecognizer:rotationGesture];
旋转手势调用的方法:
1 //旋转手势 2 -(void)rotationGesture:(id)sender 3 { 4 5 UIRotationGestureRecognizer *gesture = sender; 6 7 if (gesture.state==UIGestureRecognizerStateChanged) 8 { 9 _imageView.transform=CGAffineTransformMakeRotation(gesture.rotation); 10 } 11 12 if(gesture.state==UIGestureRecognizerStateEnded) 13 { 14 15 [UIView animateWithDuration:1 animations:^{ 16 _imageView.transform=CGAffineTransformIdentity;//取消形变 17 }]; 18 } 19 20 } |
上面的东西没有多高深的技术,就是对iOS开发中的手势做了一下小小的总结,温故一下基础知识。
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
Hessian和Burlap都是基于HTTP的,他们都解决了RMI所头疼的防火墙渗透问题。但当传递过来的RPC消息中包含序列化对象时,RMI就完胜Hessian和Burlap了。
因为Hessian和Burlap都是采用了私有的序列化机制,而RMI使用的是
Java本身的序列化机制。如果数据模型非常复杂,那么Hessian/Burlap的序列化模型可能就无法胜任了。
Spring开发团队意识到RMI服务和基于HTTP的服务之前的空白,Spring的HttpInvoker应运而生。
Spring的HttpInvoker,它基于HTTP之上提供RPC,同时又使用了Java的对象序列化机制。
程序的具体实现
一、首先我们创建一个实体类,并实现Serializable接口
package entity; import java.io.Serializable; public class Fruit implements Serializable { private String name; private String color; public String getName() { return name; } public void setName(String name) { this.name = name; } public String getColor() { return color; } public void setColor(String color) { this.color = color; } } |
二、创建一个接口
package service;
import java.util.List;
import entity.Fruit;
public interface FruitService {
List<Fruit> getFruitList();
}
三、创建一个类,并实现步骤二中的接口
package service.impl; import java.util.ArrayList; import java.util.List; import service.FruitService; import entity.Fruit; public class FruitServiceImpl implements FruitService { public List<Fruit> getFruitList() { List<Fruit> list = new ArrayList<Fruit>(); Fruit f1 = new Fruit(); f1.setName("橙子"); f1.setColor("黄色"); Fruit f2 = new Fruit(); f2.setColor("红色"); list.add(f1); list.add(f2); return list; } } |
四、在WEB-INF下的web.xml中配置SpringMVC需要的信息
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener </listener-class> </listener> <servlet> <servlet-name>springMvc</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>springMvc</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> |
五、在applicationContext.xml配置需要导出服务的bean信息
<bean id="furitService" class="service.impl.FruitServiceImpl"></bean>
<bean id="FuritService"
class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter"
p:serviceInterface="service.FruitService" p:service-ref="furitService" />
六、在WEB-INF下创建springMvc-servlet.xml文件,并配置urlMapping
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd"> <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="mappings"> <props> <prop key="/fruitService">FuritService</prop> </props> </property> </bean> </beans> |
七、在applicationContext.xml编写客户端所需要获得服务的bean信息
<bean id="getFruitService"
class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean"
p:serviceInterface="service.FruitService"
p:serviceUrl="http://localhost:8080/SpringHttpInvoker/fruitService" />
八、编写测试代码
package test; import java.util.List; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import entity.Fruit; import service.FruitService; public class Test { public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext( "applicationContext.xml"); FruitService fruitService = (FruitService) ctx .getBean("getFruitService"); List<Fruit> fruitList = fruitService.getFruitList(); for (Fruit fruit : fruitList) { System.out.println(fruit.getColor() + "的" + fruit.getName()); } } } |
将项目部署到Tomcat上,启动Tomcat服务,并运行测试代码
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
一、前言
MyBatis的update元素的用法与insert元素基本相同,因此本篇不打算重复了。本篇仅记录批量update操作的
sql语句,懂得SQL语句,那么MyBatis部分的操作就简单了。
注意:下列批量更新语句都是作为一个事务整体执行,要不全部成功,要不全部回滚。
二、MSSQL的SQL语句
WITH R AS(
SELECT 'John' as name, 18 as age, 42 as id
UNION ALL
SELECT 'Mary' as name, 20 as age, 43 as id
UNION ALL
SELECT 'Kite' as name, 21 as age, 44 as id
)
UPDATE TStudent SET name = R.name, age = R.age
FROM R WHERE R.id = TStudent.Id
三、MSSQL、ORACLE和MySQL的SQL语句 UPDATE TStudent SET Name = R.name, Age = R.age
from (
SELECT 'Mary' as name, 12 as age, 42 as id
union all
select 'John' as name , 16 as age, 43 as id
) as r
where ID = R.id
四、SQLITE的SQL语句
当条更新:
REPLACE INTO TStudent(Name, Age, ID)
VALUES('Mary', 12, 42)
批量更新:
REPLACE INTO TStudent(Name, Age, ID)
SELECT * FROM (
select 'Mary' as a, 12 as b, 42 as c
union all
select 'John' as a, 14 as b, 43 as b
) AS R
说明:REPLACE INTO会根据主键值,决定执行INSERT操作还是UPDATE操作。
五、总结
本篇突出MyBatis作为半自动ORM框架的好处了,全手动操控SQL语句怎一个爽字了得。但对码农的SQL知识要求也相对增加了不少,倘若针对项目要求再将这些进行二次封装那会轻松比少。
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
首先我们来回顾下上篇的概念: 负载均衡 == 分身的能力。
既然要有分身的能力嘛,这好办,多弄几台服务器就搞定了。
今天我们讲的实例嘛…..我们还是先看图比较好:
还是图比较清晰,以下我都用别名称呼:
PA : 负载均衡服务器/WEB入口服务器/www.mydomain.com
P1 : WEB服务器/分身1/192.168.2.3
P2 : WEB服务器/分身2/192.168.2.4
P3 : WEB服务器/分身3/192.168.2.5
PS:首先我们学这个的开始之前吧,不懂防火墙的童鞋们,建议你们把PA、P1、P2、P3的防火墙关闭,尽量不要引起不必要的麻烦。
首先 :PA、P1、P2、P3都安装了Nginx,不会安装的可以去官网查看教程(中文版教程、非常的牛X)
1. 装完之后哈,我们先找到 PA 的nginx.conf配置文件:
在http段加入以下代码:
upstream servers.mydomain.com {
server 192.168.2.4:80;
server 192.168.2.5:80;
}
当然嘛,这servers.mydomain.com随便取的。
那么PA的server配置如下:
在http段加入以下代码:
server{
listen 80;
server_name www.mydomain.com;
location / {
proxy_pass http://servers.mydomain.com;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
那么P1、P2、P3的配置如下:
server{
listen 80;
server_name www.mydomain.com; 2. 有人就问了,我用其它端口行不行啊,当然也是可以的,假设PA的nginx.conf配置文件:
upstream servers2.mydomain.com { server 192.168.2.3:8080; server 192.168.2.4:8081; server 192.168.2.5:8082; } server{ listen 80; server_name www.mydomain.com; location / { proxy_pass http://servers2.mydomain.com; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } |
那么P1的配置如下:
server{
listen 8080;
server_name www.mydomain.com;
index index.html;
root /data/htdocs/www;
}
P2配置:
server{
listen 8081;
server_name www.mydomain.com;
index index.html;
root /data/htdocs/www;
}
P3配置:
server{
listen 8082;
server_name www.mydomain.com;
index index.html;
root /data/htdocs/www;
}
重启之后,我们访问下,恩不错,确实很厉害。
当我们把一台服务器给关闭了后。
访问网址,还是OK的。说明:负载均衡还要懂得修理他(T出泡妞队营)
3. 那么负载均衡如何保持通话呢?
当然现在有好几种方案,我们这次只是讲一种。
IP哈希策略
优点:能较好地把同一个客户端的多次请求分配到同一台服务器处理,避免了加权轮询无法适用会话保持的需求。
缺点:当某个时刻来自某个IP地址的请求特别多,那么将导致某台后端服务器的压力可能非常大,而其他后端服务器却空闲的不均衡情况。
nginx的配置也很简单,代码如下:
upstream servers2.mydomain.com {
server 192.168.2.3:8080;
server 192.168.2.4:8081;
server 192.168.2.5:8082;
ip_hash;
}
其实一切就这么简单,来赶快试试吧!
4. 说了这么多,其实你有没有发现一个问题的所在,就是这么多服务器,他们共同需要的文件从哪里来?
想知道如何解决,请继续关注:负载均衡 ---- 文件服务策略
index index.html;
root /data/htdocs/www;
}
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
最近使用loadrunner压测一个项目的时候,发现TPS波动巨大、且平均值较低。使用jmeter压测则没有这个问题。经过多方排查发现一个让人极度费解的原因:
原脚本:
//脚本其他代码 ...... web_submit_data("aaa", "Action=http://demo.ddd.com/aaa?a=xr23498isfgljfsfd&b=adfasdfoi4308askdfjkla", //此处为密文链接 "Method=POST", "RecContentType=text/html", "Referer=http://demo.ddd.com/ccc?a=xr23498isfgljfsfd&b=adfasdfoi4308askdfjkla", "Snapshot=t2.inf", "Mode=HTTP", ITEMDATA, LAST); //事务判断逻辑等代码 ..... |
TPS图如下:
修改后的代码:
//脚本其他代码 ...... web_submit_data("aaa", "Action=http://demo.ddd.com/aaa?a=xr23498isfgljfsfd&b=adfasdfoi4308askdfjkla", //此处为密文链接 "Method=GET", "RecContentType=text/html", "Referer=http://demo.ddd.com/ccc?a=xr23498isfgljfsfd&b=adfasdfoi4308askdfjkla", "Snapshot=t2.inf", "Mode=HTTP", ITEMDATA, LAST); //事务判断逻辑等代码 ..... |
问题得以解决。后来猜测是否loadrunner对于URL中加密的参数/值对兼容性有问题?
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
让你的报告作为软件质量
测试的一部分,以一个简单,快速和直观的方式将信息呈现给观众。
这里有9基本原则要遵循来有效的报告你的
性能测试结果。
及时报告,经常报告
经常地共享数据和信息对于使您的测试项目整体成功来说是至关重要的。为了有效的做到这一点,每隔几天向代理人和项目小组以邮件方式发送总结图表,其中图表包含对所有要点简明扼要的说明。
利用视觉方式
许多人发现以视觉方式汇报统计数据更容易让人理解。在对性能结果的数据方面尤为正确,特别是对大数据量,通过此方式更容易通过数据来识别有价值的模式。
让你的报表直观
你的报表应该更直观,应该快速清楚地配合您的演讲。确保直观的方法有,从你的图表中去除所有的标签,并通过叙述来说明。
用适当的统计数据
即便多样的统计数据概念的必须性被广泛认可,仍有
软件测试人员,开发人员和管理人员并不擅长这一点。因此,如果你没有信心,应该用哪一类统计类型来说明某个问题,就请别人来帮助。
正确地巩固你的数据
并不是必须要巩固你获得的结果,但是通过这种方式将你的结果合并到几幅图中去被证实更易于阅读。另外,要记住,只有相同的,类似的统计测试的执行结果,可并入你的执行情况报告中的同个图表中去。
有效的总结你的数据
当你对结果进行总结后,他们更可能能够更好地展示各个模式的意义。将你的图表总结起来来显示数据,同时对各种测试的执行情况进行显示,就可以更容易的分析模式和趋势。
定制你的报告
通常会有三类人读您的性能测试报告:你的团队的技术人员,非技术人员和非团队成员的客户。在报告之前,确保你知道你的听众及其期望,然后决定用什么方式来发表你的结果。
简明口头总结
你的结果中至少应该包含一些简短的口头总结,同时也有部分结果更容易通过字面来描述。基于目标受众来决定哪些应包括在口头总结中。
使你的数据可获得
数据对于不同的人在不同的时间有着不同的价值。通过这个方法你也可以最大限度地减少大家认为你的性能测试结果只是通过某些他们无法理解的流程和工具获得的胡编乱造的结果。
每次根据上述原则,你一定能够完成一个极好的根据你的测试策略来完成的性能测试报告。
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
SHOW VARIABLES LIKE '%partition%';
+-------------------+-------+
| Variable_name | Value |
+-------------------+-------+
| have_partitioning | YES |
+-------------------+-------+
如果VALUE 为YES 则支持分区,
2.测试那种存储引擎支持分区
INOODB引擎 mysql> Create table engine1(id int) engine=innodb partition by range(id)(partition po values less than(10)); Query OK, 0 rows affected (0.01 sec) MRG_MYISAM引擎 mysql> Create table engine2(id int) engine=MRG_MYISAM partition by range(id)(partition po values less than(10)); ERROR 1572 (HY000): Engine cannot be used in partitioned tables blackhole引擎 mysql> Create table engine3(id int) engine=blackhole partition by range(id)(partition po values less than(10)); Query OK, 0 rows affected (0.01 sec) CSV引擎 mysql> Create table engine4(id int) engine=csv partition by range(id)(partition po values less than(10)); ERROR 1572 (HY000): Engine cannot be used in partitioned tables Memory引擎 mysql> Create table engine5(id int) engine=memory partition by range(id)(partition po values less than(10)); Query OK, 0 rows affected (0.01 sec) federated引擎 mysql> Create table engine6(id int) engine=federated partition by range(id)(partition po values less than(10)); Query OK, 0 rows affected (0.01 sec) archive引擎 mysql> Create table engine7(id int) engine=archive partition by range(id)(partition po values less than(10)); Query OK, 0 rows affected (0.01 sec) myisam 引擎 mysql> Create table engine8(id int) engine=myisam partition by range(id)(partition po values less than(10)); Query OK, 0 rows affected (0.01 sec) |
表分区的存储引擎相同
mysql> Create table pengine1(id int) engine=myisam partition by range(id)(partition po values less than(10) engine=myisam, partition p1 values less than(20) engine=myisam);
Query OK, 0 rows affected (0.05 sec)
表分区的存储引擎不同
mysql> Create table pengine2(id int) engine=myisam partition by range(id)(partition po values less than(10) engine=myisam, partition p1 values less than(20) engine=innodb);
ERROR 1497 (HY000): The mix of handlers in the partitions is not allowed in this version of MySQL
同一个分区表中的所有分区必须使用同一个存储引擎,并且存储引擎要和主表的保持一致。
4.分区类型
Range:基于一个连续区间的列值,把多行分配给分区;
LIST:列值匹配一个离散集合;
Hash:基于用户定义的表达式的返回值选择分区,表达式对要插入表中的列值进行计算。这个函数可以包含SQL中有效的,产生非负整
数值的任何表达式。
KEY:类似于HASH分区,区别在于KEY 分区的表达式可以是一列或多列,且MYSQL提供自身的HASH函数。
5.RANGE分区MAXVALUE值 及加分区测试;
创建表 PRANGE,最后分区一个分区值是MAXVALUE
mysql> Create table prange(id int) engine=myisam partition by range(id)(partition po values less than(10), partition p1 values less than(20),partition p2 values less than maxvalue);
Query OK, 0 rows affected (0.06 sec)
加分区
mysql> alter table prange add partition (partition p3 values less than (20));
ERROR 1481 (HY000): MAXVALUE can only be used in last partition definition
在分区P0前面加个分区
mysql> alter table prange add partition (partition p3 values less than (1));
ERROR 1481 (HY000): MAXVALUE can only be used in last partition definition
说明有MAXVALUE值后,直接加分区是不可行的;
创建表PRANGE1,无MAXVALUE值
mysql> Create table prange1(id int) engine=myisam partition by range(id)(partition po values less than(10), partition p1 values less than(20),partition p2 values less than (30)); www.2cto.com
Query OK, 0 rows affected (0.08 sec)
从最大值后加个分区
mysql> alter table prange1 add partition (partition p3 values less than (40));
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0
从分区的最小值前加个分区
mysql> alter table prange1 add partition (partition p43 values less than (1));
ERROR 1493 (HY000): VALUES LESS THAN value must be strictly increasing for each partition
由此可见,RANGE 的分区方式在加分区的时候,只能从最大值后面加,而最大值前面不可以添加;
6. 用时间做分区测试
create table ptime2(id int,createdate datetime) engine=myisam partition by range (to_days(createdate))
(partition po values less than (20100801),partition p1 values less than (20100901));
Query OK, 0 rows affected (0.01 sec)
mysql> create table ptime3(id int,createdate datetime) engine=myisam partition by range (createdate)
(partition po values less than (20100801),partition p1 values less than (20100901));
ERROR 1491 (HY000): The PARTITION function returns the wrong type
直接使用时间列不可以,RANGE分区函数返回的列需要是整型。
mysql> create table ptime6(id int,createdate datetime) engine=myisam partition by range (year(createdate))
(partition po values less than (2010),partition p1 values less than (2011));
Query OK, 0 rows affected (0.01 sec)
使用年函数也可以分区。
7.Mysql可用的分区函数
DAY() DAYOFMONTH() DAYOFWEEK() DAYOFYEAR() DATEDIFF() EXTRACT() HOUR() MICROSECOND() MINUTE() MOD() MONTH() QUARTER() SECOND() TIME_TO_SEC() TO_DAYS() WEEKDAY() YEAR() YEARWEEK() 等 |
当然,还有FLOOR(),CEILING() 等,前提是使用这两个分区函数的分区健必须是整型。
要小心使用其中的一些函数,避免犯逻辑性的错误,引起全表扫描。
比如:
create table ptime11(id int,createdate datetime) engine=myisam partition by range (day(createdate)) (partition po values less than (15),partition p1 values less than (31)); mysql> insert into ptime11 values (1,'2010-06-17'); mysql> explain partitions select count(1) from ptime11 where createdate>'2010-08-17'\G; *************************** 1. row *************************** id: 1 select_type: SIMPLE table: ptime11 partitions: po,p1 type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 5 Extra: Using where 1 row in set (0.00 sec) |
8.主键及约束测试
分区健不包含在主键内
mysql> create table pprimary(id int,createdate datetime,primary key(id)) engine=myisam partition by range (day(createdate)) (partition po values less than (15),partition p1 values less than (31)); www.2cto.com
ERROR 1503 (HY000): A PRIMARY KEY must include all columns in the table's partitioning function
分区健包含在主键内
mysql> create table pprimary1(id int,createdate datetime,primary key(id,createdate)) engine=myisam partition by range (day(createdate)) (partition po values less than (15),partition p1 values less than (31));
Query OK, 0 rows affected (0.05 sec)
说明分区健必须包含在主键里面。
mysql> create table pprimary2(id int,createdate datetime,uid char(10),primary key(id,createdate),unique key(uid)) engine=myisam partition by range(to_days(createdate))(partition p0 values less than (20100801),partition p1 values less than (20100901));
ERROR 1503 (HY000): A UNIQUE INDEX must include all columns in the table's partitioning function
说明在表上建约束索引会有问题,必须把约束索引列包含在分区健内。
mysql> create table pprimary3(id int,createdate datetime,uid char(10),primary key(id,createdate),unique key(createdate)) engine=myisam partition by range(to_days(createdate))(partition p0 values less than (20100801),partition p1 values less than (20100901));
Query OK, 0 rows affected (0.00 sec)
虽然在表上可以加约束索引,但是只有包含在分区健内,这种情况在实际应用过程中会遇到问题,这个问题点在以后的MYSQL 版本中也许会改进。
9.子分区测试
只有RANGE和LIST分区才能有子分区,每个分区的子分区数量必须相同,
mysql> create table pprimary7(id int,createdate datetime,uid char(10),primary key(id,createdate)) engine=myisam partition by range(to_days(createdate)) subpartition by hash(to_days(createdate))(partition p0 values less than (20100801) ( subpartition so,subpartition s1) ,partition p1 values less than (20100901) (subpartition s0,subpartition s1)); www.2cto.com
ERROR 1517 (HY000): Duplicate partition name s1
提示了重复的分区名称错误,这和MYSQL5.1帮助文档中的说明有出入,不知道是不是这个问题在某个小版本中修改过。
10.MYSQL分区健NULL值测试;
MYSQL将NULL值视为0.自动插入最小的分区中。
11.MYSQL分区管理测试
mysql> alter table pprimary4 truncate partition p1;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'truncate partition p1' at line 1
5.1版本中还不支持这个语法,5.5中已经支持,很好的一个命令;
ALTER TABLE reorganize 可以重新组织分区。
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
一、简单示例
说明:使用APP主流UI框架结构完成简单的界面搭建
搭建页面效果:
二、搭建过程和注意点
1.新建一个项目,把原有的控制器删除,添加UITabBarController控制器作为管理控制器
2.对照界面完成搭建
3.注意点:
(1)隐藏工具条:配置一个属性,Hideabotton bar在push的时候隐藏底部的bar在那个界面隐藏,就在哪个界面设置。
(2).cell可以设置行高
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters
SQL Server 认证可以运行以下语句来查询
1 select * from sys.sql_logins
管理员可以直接修改密码,但无法知晓原有密码原文,SQL Server使用混淆算法来保护安全性不如Windows 身份认证,
Windows认证模式
首先分为本机账号与域账号
SQL Server 将认证和授权分散给了不同的对象来完成,SQL Server 的“登入名”(Login)用于认证,连接SQL Server 的SQL或者Windows 账户必须在SQL Server中有对应的登入名才能成功登入。
而每个
数据库中的“用户”(User)被授予了操作数据库中对象的相应权限。登入名和用户之间通过SID联系起来,于是登入SQL Server 的登入名也获得了操作数据库的相应权限。
这个机制带来以下两个问题:
1.提高了高可用解决方案的维护成本。msdb(系统数据库)无法被镜像。类似制作数据库镜像系统,就需同时在主体和镜像服务器上的添加同样的用户名密码,否则发生故障转移,镜像服务就无法使用新的登入名进行登入。另外,在镜像服务器上添加登入名时要确保和主体服务器上的登入名使用相同的SID,否则就会破坏登入名到数据库用户之间的对应关系。成为所谓的孤立账户。
2.增加了迁移数据库的复杂性。不能仅仅简单地迁移用户数据数据库和程序。因为还有一部分和应用相关的对象遗漏在用户数据库之外,其中包括登入名。在迁移应用的时候,登入名需被单独的从老的环境中提取出来,在部署到新的环境上。
前提是数据库兼容级别110以上,即2012以上。包含数据库创建。。
1 EXEC sys.sp_configure N'contained database authentication', N'1'
2 GO
3 RECONFIGURE WITH OVERRIDE
4 GO
修改[AdventureWorks2012]为包含数据库
1 USE [master]
2 GO
3 ALTER DATABASE [AdventureWorks2012] SET CONTAINMENT = PARTIAL WITH NO_WAIT
4 GO
查询实例中所有的包含数据库
1 use master
2 select * from sys.databases
3 where containment >0
将现有的数据库用户改为包含数据库用户
1 USE [AdventureWorks2012] 2 GO 3 DECLARE @username SYSNAME; 4 DECLARE user_cursor CURSOR 5 FOR 6 SELECT dp.name 7 FROM sys.database_principals AS dp 8 JOIN sys.server_principals AS sp ON dp.sid = sp.sid 9 WHERE dp.authentication_type = 1 10 AND sp.is_disabled = 0; 11 OPEN user_cursor 12 FETCH NEXT FROM user_cursor INTO @username 13 WHILE @@FETCH_STATUS = 0 14 BEGIN 15 EXECUTE sp_migrate_user_to_contained @username = @username, 16 @rename = N'keep_name', @disablelogin = N'disable_login'; 17 FETCH NEXT FROM user_cursor INTO @username 18 END 19 CLOSE user_cursor; 20 DEALLOCATE user_cursor; |
English » | | | | | | | | |
Text-to-speech function is limited to 100 characters