單元測試并不能證明你的代碼是正確的,只能證明你的代碼是沒有錯誤的。
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