qileilove

blog已经转移至github,大家请访问 http://qaseven.github.io/

Burpsuite截断上传拿webshell

 很多大大都在用burpsiute,大大们说这个工具的功能有很多很多,小菜我也不知到底是些什么。然后找了篇大大写的Burpsiute截断上传拿webshell的科普帖看了看,然后去实践了一下。
  在后台上传图片的地方添加xxx.jpg 图片
  打开burpsiute
  进入proxy后进入options修改代理端口等
  然后设置浏览器
 在浏览器里提交上传截取到数据
  修改/pimage => /pimage/xx.asp
  最后把空格修改
  得到webshell地址

posted @ 2014-12-03 13:44 顺其自然EVO 阅读(449) | 评论 (0)编辑 收藏

LoadRunner脚本开发-字符串编码转换

 相关函数
  lr_convert_string_encoding函数
  功能:字符串编码转换
  原型:
  int lr_convert_string_encoding(const char *sourceString, const char *fromEncoding, const char *toEncoding, const char *paramName);
  返回值:0(执行成功)、-1(执行失败)
  参数说明:
  sourceString:要转换的字符串
  fromEncoding:源字符的编码
  toEncoding:保存在参数parmaName中的字符串编码,即要转换的目标编码
  paramName:保存转换编码后的字符串
  说明:
  1.lr_convert_string_encoding支持system locale,Unicode,UTF-8字符串编码的相互转换,参数paramName中保存结果字符串,该结果字符串包含字符串结束符NULL
  2.结果字符串中的可打印字符在VuGen和日志中按实际字符显示,不可打印字符则以十六进制显示,例如:
  rc = lr_convert_string_encoding("A", NULL, LR_ENC_UTF8, "stringInUnicode");
  结果字符串(即stringInUnicode参数值)显示为:A\x00, 而不是\x41\x00,因为A为可打印字符串.
  3.fromEncoding and toEncoding可选值:
  loadrunner <wbr>脚本开发-字符串编码转换
  例子:
Action()
{
int rc = 0;
char *converted_buffer_unicode = NULL;
rc = lr_convert_string_encoding("hello", NULL, LR_ENC_UNICODE, "stringInUnicode");
if(rc < 0)
{
lr_output_message("convert_string_encoding failed ");
// error
}
return 0;
}
  输出结果:

posted @ 2014-12-03 13:44 顺其自然EVO 阅读(980) | 评论 (0)编辑 收藏

网页主动探测工具使用

 单位的项目是IBatis做的,每个查询的SQL里面都有很多判断
  上次优化SQL之后,其中的一个分支报错,但是作为dba,不可能排查每一个分支.
  所以,干脆用爬虫爬过所有的网页,主动探测程序的异常.
  这样有两个好处
  1.可以主动查看网页是否异常 (500错误,404错误)
  2.可以筛查速度较慢的网页,从这个方向也可以定位慢SQL吧.(也有服务器资源不足,造成网络超时的情况)
  前提,
  必须是互联网公司,大多数网页不用登录也可以浏览
  首先,建表
  CREATE SEQUENCE seq_probe_id INCREMENT BY 1 START WITH 1 NOMAXvalue NOCYCLE CACHE 2000;
  create table probe(
  id int primary key,
  host varchar(40) not null,
  path varchar(500) not null,
  state int not null,
  taskTime int not null,
  type varchar(10) not null,
  createtime date default sysdate not null
  ) ;
  其中host是域名,path是网页的相对路径,state是HTTP状态码,taskTime是网页获取时间,单位是毫秒,type是类型(html,htm,jpg等)
  程序结构
  程序分三个主要步骤,再分别用三个队列实现生产者消费者模式.
  1.连接.根据连接队列的目标,使用Socket获取网页,然后放入解析队列
  2.解析.根据解析队列的内容,使用正则表达式获取该网页的合法连接,将其再放入连接队列.然后将解析的网页放入持久化队列
  3.持久化.将持久化队列的内容存入数据库,以便查询。
  程序使用三个步骤并行,每个步骤可以并发的方式.
但是通常来说,解析和持久化可以分别用单线程的方式执行.
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Probe {
private static final BlockingQueue<Task> CONNECTLIST = new LinkedBlockingQueue<Task>();
private static final BlockingQueue<Task> PARSELIST = new LinkedBlockingQueue<Task>();
private static final BlockingQueue<Task> PERSISTENCELIST = new LinkedBlockingQueue<Task>();
private static ExecutorService CONNECTTHREADPOOL;
private static ExecutorService PARSETHREADPOOL;
private static ExecutorService PERSISTENCETHREADPOOL;
private static final List<String> DOMAINLIST = new CopyOnWriteArrayList<>();
static {
CONNECTTHREADPOOL = Executors.newFixedThreadPool(200);
PARSETHREADPOOL = Executors.newSingleThreadExecutor();
PERSISTENCETHREADPOOL = Executors.newFixedThreadPool(1);
DOMAINLIST.add("域名");
}
public static void main(String args[]) throws Exception {
long start = System.currentTimeMillis();
CONNECTLIST.put(new Task("域名", 80, "/static/index.html"));
for (int i = 0; i < 600; i++) {
CONNECTTHREADPOOL.submit(new ConnectHandler(CONNECTLIST, PARSELIST));
}
PARSETHREADPOOL.submit(new ParseHandler(CONNECTLIST, PARSELIST, PERSISTENCELIST, DOMAINLIST));
PERSISTENCETHREADPOOL.submit(new PersistenceHandler(PERSISTENCELIST));
while (true) {
Thread.sleep(1000);
long end = System.currentTimeMillis();
float interval = ((end - start) / 1000);
int connectTotal = ConnectHandler.GETCOUNT();
int parseTotal = ParseHandler.GETCOUNT();
int persistenceTotal = PersistenceHandler.GETCOUNT();
int connectps = Math.round(connectTotal / interval);
int parseps = Math.round(parseTotal / interval);
int persistenceps = Math.round(persistenceTotal / interval);
System.out.print("\r连接总数:" + connectTotal + " \t每秒连接:" + connectps + "\t连接队列剩余:" + CONNECTLIST.size()
+ " \t解析总数:" + parseTotal + " \t每秒解析:" + parseps + "\t解析队列剩余:" + PARSELIST.size() + " \t持久化总数:"
+ persistenceTotal + " \t每秒持久化:" + persistenceps + "\t持久化队列剩余:" + PERSISTENCELIST.size());
}
}
}
class Task {
public Task() {
}
public void init(String host, int port, String path) {
this.setCurrentPath(path);
this.host = host;
this.port = port;
}
public Task(String host, int port, String path) {
init(host, port, path);
}
private String host;
private int port;
private String currentPath;
private long taskTime;
private String type;
private String content;
private int state;
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public String getCurrentPath() {
return currentPath;
}
public void setCurrentPath(String currentPath) {
this.currentPath = currentPath;
this.type = currentPath.substring(currentPath.indexOf(".") + 1,
currentPath.indexOf("?") != -1 ? currentPath.indexOf("?") : currentPath.length());
}
public long getTaskTime() {
return taskTime;
}
public void setTaskTime(long taskTime) {
this.taskTime = taskTime;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getHost() {
return host;
}
public int getPort() {
return port;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
class ParseHandler implements Runnable {
private static Set<String> SET = new ConcurrentSkipListSet<String>();
public static int GETCOUNT() {
return COUNT.get();
}
private static final AtomicInteger COUNT = new AtomicInteger();
private BlockingQueue<Task> connectlist;
private BlockingQueue<Task> parselist;
private BlockingQueue<Task> persistencelist;
List<String> domainlist;
private interface Filter {
void doFilter(Task fatherTask, Task newTask, String path, Filter chain);
}
private class FilterChain implements Filter {
private List<Filter> list = new ArrayList<Filter>();
{
addFilter(new TwoLevel());
addFilter(new OneLevel());
addFilter(new FullPath());
addFilter(new Root());
addFilter(new Default());
}
private void addFilter(Filter filter) {
list.add(filter);
}
private Iterator<Filter> it = list.iterator();
@Override
public void doFilter(Task fatherTask, Task newTask, String path, Filter chain) {
if (it.hasNext()) {
it.next().doFilter(fatherTask, newTask, path, chain);
}
}
}
private class TwoLevel implements Filter {
@Override
public void doFilter(Task fatherTask, Task newTask, String path, Filter chain) {
if (path.startsWith("../../")) {
String prefix = getPrefix(fatherTask.getCurrentPath(), 3);
newTask.init(fatherTask.getHost(), fatherTask.getPort(), path.replace("../../", prefix));
} else {
chain.doFilter(fatherTask, newTask, path, chain);
}
}
}
private class OneLevel implements Filter {
@Override
public void doFilter(Task fatherTask, Task newTask, String path, Filter chain) {
if (path.startsWith("../")) {
String prefix = getPrefix(fatherTask.getCurrentPath(), 2);
newTask.init(fatherTask.getHost(), fatherTask.getPort(), path.replace("../", prefix));
} else {
chain.doFilter(fatherTask, newTask, path, chain);
}
}
}
private class FullPath implements Filter {
@Override
public void doFilter(Task fatherTask, Task newTask, String path, Filter chain) {
if (path.startsWith("http://")) {
Iterator<String> it = domainlist.iterator();
boolean flag = false;
while (it.hasNext()) {
String domain = it.next();
if (path.startsWith("http://" + domain + "/")) {
newTask.init(domain, fatherTask.getPort(), path.replace("http://" + domain + "/", "/"));
flag = true;
break;
}
}
if (!flag) {
newTask = null;
}
} else {
chain.doFilter(fatherTask, newTask, path, chain);
}
}
}
private class Root implements Filter {
@Override
public void doFilter(Task fatherTask, Task newTask, String path, Filter chain) {
if (path.startsWith("/")) {
newTask.init(fatherTask.getHost(), fatherTask.getPort(), path);
} else {
chain.doFilter(fatherTask, newTask, path, chain);
}
}
}
private class Default implements Filter {
@Override
public void doFilter(Task fatherTask, Task newTask, String path, Filter chain) {
String prefix = getPrefix(fatherTask.getCurrentPath(), 1);
newTask.init(fatherTask.getHost(), fatherTask.getPort(), prefix + "/" + path);
}
}
public ParseHandler(BlockingQueue<Task> connectlist, BlockingQueue<Task> parselist,
BlockingQueue<Task> persistencelist, List<String> domainlist) {
this.connectlist = connectlist;
this.parselist = parselist;
this.persistencelist = persistencelist;
this.domainlist = domainlist;
}
private Pattern pattern = Pattern.compile("\"[^\"]+\\.htm[^\"]*\"");
private void handler() {
try {
Task task = parselist.take();
parseTaskState(task);
if (200 == task.getState()) {
Matcher matcher = pattern.matcher(task.getContent());
while (matcher.find()) {
String path = matcher.group();
if (!path.contains(" ") && !path.contains("\t") && !path.contains("(") && !path.contains(")")
&& !path.contains(":")) {
path = path.substring(1, path.length() - 1);
if (!SET.contains(path)) {
SET.add(path);
createNewTask(task, path);
}
}
}
}
task.setContent(null);
persistencelist.put(task);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void parseTaskState(Task task) {
if (task.getContent().startsWith("HTTP/1.1")) {
task.setState(Integer.parseInt(task.getContent().substring(9, 12)));
} else {
task.setState(Integer.parseInt(task.getContent().substring(19, 22)));
}
}
/**
* @param fatherTask
* @param path
* @throws Exception
*/
private void createNewTask(Task fatherTask, String path) throws Exception {
Task newTask = new Task();
FilterChain filterchain = new FilterChain();
filterchain.doFilter(fatherTask, newTask, path, filterchain);
if (newTask != null) {
connectlist.put(newTask);
}
}
private String getPrefix(String s, int count) {
String prefix = s;
while (count > 0) {
prefix = prefix.substring(0, prefix.lastIndexOf("/"));
count--;
}
return "".equals(prefix) ? "/" : prefix;
}
@Override
public void run() {
while (true) {
this.handler();
COUNT.addAndGet(1);
}
}
}
class ConnectHandler implements Runnable {
public static int GETCOUNT() {
return COUNT.get();
}
private static final AtomicInteger COUNT = new AtomicInteger();
private BlockingQueue<Task> connectlist;
private BlockingQueue<Task> parselist;
public ConnectHandler(BlockingQueue<Task> connectlist, BlockingQueue<Task> parselist) {
this.connectlist = connectlist;
this.parselist = parselist;
}
private void handler() {
try {
Task task = connectlist.take();
long start = System.currentTimeMillis();
getHtml(task);
long end = System.currentTimeMillis();
task.setTaskTime(end - start);
parselist.put(task);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void getHtml(Task task) throws Exception {
StringBuilder sb = new StringBuilder(2048);
InetAddress addr = InetAddress.getByName(task.getHost());
// 建立一个Socket
Socket socket = new Socket(addr, task.getPort());
// 发送命令,无非就是在Socket发送流的基础上加多一些握手信息,详情请了解HTTP协议
BufferedWriter wr = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"));
wr.write("GET " + task.getCurrentPath() + " HTTP/1.0\r\n");
wr.write("HOST:" + task.getHost() + "\r\n");
wr.write("Accept:*/*\r\n");
wr.write("\r\n");
wr.flush();
// 接收Socket返回的结果,并打印出来
BufferedReader rd = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line;
while ((line = rd.readLine()) != null) {
sb.append(line);
}
wr.close();
rd.close();
task.setContent(sb.toString());
socket.close();
}
@Override
public void run() {
while (true) {
this.handler();
COUNT.addAndGet(1);
}
}
}
class PersistenceHandler implements Runnable {
static {
try {
Class.forName("oracle.jdbc.OracleDriver");
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public static int GETCOUNT() {
return COUNT.get();
}
private static final AtomicInteger COUNT = new AtomicInteger();
private BlockingQueue<Task> persistencelist;
public PersistenceHandler(BlockingQueue<Task> persistencelist) {
this.persistencelist = persistencelist;
try {
conn = DriverManager.getConnection("jdbc:oracle:thin:127.0.0.1:1521:orcl", "edmond", "edmond");
ps = conn
.prepareStatement("insert into probe(id,host,path,state,tasktime,type) values(seq_probe_id.nextval,?,?,?,?,?)");
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private Connection conn;
private PreparedStatement ps;
@Override
public void run() {
while (true) {
this.handler();
COUNT.addAndGet(1);
}
}
private void handler() {
try {
Task task = persistencelist.take();
ps.setString(1, task.getHost());
ps.setString(2, task.getCurrentPath());
ps.setInt(3, task.getState());
ps.setLong(4, task.getTaskTime());
ps.setString(5, task.getType());
ps.executeUpdate();
conn.commit();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
  ParseHandler 使用了一个职责链模式,
  TwoLevel 处理../../开头的连接(../../sucai/sucai.htm)
  OneLevel 处理../开头的连接(../sucai/sucai.htm)
  FullPath 处理绝对路径的连接(http://域名/sucai/sucai.htm)
  Root 处理/开头的连接(/sucai/sucai.htm)
  Default 处理常规的连接(sucai.htm)
  ParseHandler FullPath 过滤需要一个白名单.
  这样可以使程序在固定的域名爬行
  ParseHandler parseTaskState 解析状态码 可能需要根据实际情况进行调整
  比如网页404,服务器可能会返回一个错误页,而不是通常的HTTP状态码。
  第一版仅仅实现了功能,错误处理不完整,
  所以仅仅在定制的域名下生效,其实并不通用,后续会逐步完善.

posted @ 2014-12-03 13:43 顺其自然EVO 阅读(190) | 评论 (0)编辑 收藏

JUnit测试框架的使用经验分享

1、学习Junit框架的使用
  可通过以下两个示例进行学习
  A、Junit使用方法示例1
  1)把Junit引入当前项目库中
  新建一个 Java 工程—coolJUnit,打开项目coolJUnit 的属性页 -> 选择“Java Build Path”子选项 -> 点选“Add Library…”按钮 -> 在弹出的“Add Library”对话框中选择 JUnit,并在下一页中选择版本 Junit 4 后点击“Finish”按钮。这样便把 JUnit 引入到当前项目库中了。
  2)新建单元测试代码目录
  单元测试代码是不会出现在最终软件产品中的,所以最好为单元测试代码与被测试代码创建单独的目录,并保证测试代码和被测试代码使用相同的包名。这样既保证了代码的分离,同时还保证了查找的方便。遵照这条原则,在项目 coolJUnit 根目录下添加一个新目录 testsrc,并把它加入到项目源代码目录中。
  3)在工程中添加类
  添加类SampleCaculator,类中有两个方法,分别计算加减法。编译代码。
public class SampleCalculator {
//计算两整数之和
public int add(int augend, int addend){
return augend + addend;
}
//计算两整数之差
public int subtration(int minuend, int subtrahend){
return minuend - subtrahend;
}
}
  4)写单元测试代码
  为类SampleCalculator添加测试用例。在资源管理器SampleCalculator.java文件处右击选new>选Junit Test Case(见图4),Source foler选择testsrc目录,点击next,选择要测试的方法,这里把add和subtration方法都选上,最后点finish完成。
  Junit自动生成测试类SampleCalculatorTest,修改其中的代码(如下)。
  其中assertEquals断言,用来测试预期目标和实际结果是否相等。
  assertEquals( [Sting message], expected, actual )
  expected是期望值(通常都是硬编码的),actual是被测试代码实际产生的值,message是一个可选的消息,如果提供的话,将会在发生错误时报告这个消息。
  如想用断言来比较浮点数(在Java中是类型为float或者double的数),则需指定一个额外的误差参数。
  assertEquals([Sting message], expected, actual, tolerance)
  其它断言参见课本和参考书介绍。测试方法需要按照一定的规范书写:
  1. 测试方法必须使用注解 org.junit.Test 修饰。
  2. 测试方法必须使用 public void 修饰,而且不能带有任何参数。
  5)查看运行结果
  在测试类上点击右键,在弹出菜单中选择 Run As JUnit Test。运行结果如下图,绿色的进度条提示我们,测试运行通过了。
 B、Junit使用方法示例2
  1)在工程中添加类
  类WordDealUtil中的方法wordFormat4DB( )实现的功能见文件注释。
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class WordDealUtil {
/**
* 将Java对象名称(每个单词的头字母大写)按照
* 数据库命名的习惯进行格式化
* 格式化后的数据为小写字母,并且使用下划线分割命名单词
* 例如:employeeInfo 经过格式化之后变为 employee_info
* @param name Java对象名称
*/
public static String wordFormat4DB(String name){
Pattern p = Pattern.compile("[A-Z]");
Matcher m = p.matcher(name);
StringBuffer strBuffer = new StringBuffer();
while(m.find()){
//将当前匹配子串替换为指定字符串,
//并且将替换后的子串以及其之前到上次匹配子串之后的字符串段添加到一个StringBuffer对象里
m.appendReplacement(strBuffer, "_"+m.group());
}
//将最后一次匹配工作后剩余的字符串添加到一个StringBuffer对象里
return m.appendTail(strBuffer).toString().toLowerCase();
}
}
  2)写单元测试代码
import static org.junit.Assert.*;
import org.junit.Test;
public class WordDealUtilTest {
@Test
public void testWordFormat4DB() {
String target = "employeeInfo";
String result = WordDealUtil.wordFormat4DB(target);
assertEquals("employee_info", result);
}
}
  3)进一步完善测试用例
  单元测试的范围要全面,如对边界值、正常值、错误值的测试。运用所学的测试用例的设计方法,如:等价类划分法、边界值分析法,对测试用例进行进一步完善。继续补充一些对特殊情况的测试:
//测试 null 时的处理情况
@Test public void wordFormat4DBNull(){
String target = null;
String result = WordDealUtil.wordFormat4DB(target);
assertNull(result);
}
//测试空字符串的处理情况
@Test public void wordFormat4DBEmpty(){
String target = "";
String result = WordDealUtil.wordFormat4DB(target);
assertEquals("", result);
}
//测试当首字母大写时的情况
@Test public void wordFormat4DBegin(){
String target = "EmployeeInfo";
String result = WordDealUtil.wordFormat4DB(target);
assertEquals("employee_info", result);
}
//测试当尾字母为大写时的情况
@Test public void wordFormat4DBEnd(){
String target = "employeeInfoA";
String result = WordDealUtil.wordFormat4DB(target);
assertEquals("employee_info_a", result);
}
//测试多个相连字母大写时的情况
@Test public void wordFormat4DBTogether(){
String target = "employeeAInfo";
String result = WordDealUtil.wordFormat4DB(target);
assertEquals("employee_a_info", result);
}
  4)查看分析运行结果,修改错误代码
  再次运行测试。JUnit 运行界面提示我们有两个测试情况未通过测试(见图6),当首字母大写时得到的处理结果与预期的有偏差,造成测试失败(failure);而当测试对 null 的处理结果时,则直接抛出了异常——测试错误(error)。显然,被测试代码中并没有对首字母大写和 null 这两种特殊情况进行处理,修改如下:
//修改后的方法wordFormat4DB
public static String wordFormat4DB(String name){
if(name == null){
return null;
}
Pattern p = Pattern.compile("[A-Z]");
Matcher m = p.matcher(name);
StringBuffer sb = new StringBuffer();
while(m.find()){
if(m.start() != 0)
m.appendReplacement(sb, ("_"+m.group()).toLowerCase());
}
return m.appendTail(sb).toString().toLowerCase();
}
  2、使用Junit框架对类Date和类DateUtil(参见附录)进行单元测试。
  只对包含业务逻辑的方法进行测试,包括:
  类Date中的
  isDayValid(int year, int month, int day)
  isMonthValid(int month)
  isYearValid(int year)
  类DateUtil中的
  isLeapYear(int year)
  getDayofYear(Date date)

posted @ 2014-12-03 13:43 顺其自然EVO 阅读(365) | 评论 (0)编辑 收藏

质量管理失败的10大原因

 质量管理是企业管理的重要组成部分,其重要作用众所周知。然而,在实际生产经营中,质量管理这张答卷却并非每个企业都能出色回答。依笔者所见,其中主要原因有10个。
  一:缺少远见
  远见是指洞察未来从而决定企业将要成为什么样企业的远大眼光,它能识别潜在的机会并提出目标,现实地反映了将来所能获得的利益。远见提供了企业向何处发展、企业如何制定行动计划以及企业实施计划所需要的组织结构和系统的顺序。缺少远见就导致把质量排斥在战略之外,这样企业的目标及优先顺序就不明确,质量在企业中的角色就不易被了解。要想从努力中获得成功,企业需要转变其思维方式,创造不断改进质量的环境。
  二:没有以顾客为中心
  误解顾客意愿、缺少超前为顾客服务的意识,虽改进了一些工作但没有给顾客增加价值,也会导致质量管理的失败。例如,传递公司着迷于准时传递,努力把准时从42%提高到92%,然而令管理者惊讶的是公司失去了市场,原因是公司强调了时间准时却没有时间回答顾客的电话和解释产品。顾客满意是一个动态的持续变化的目标,要想质量管理成功就必须集中精力了解顾客的期望,开发的项目要满足或超出顾客的需要。国外一家公司声称对不满意顾客提供全部赔偿,公司为此付出了代价,但收入却直线上升,员工的流动率也从117%降至50%。
  三:管理者贡献不够
  调查表明,大多数质量管理活动的失败不是技术而是管理方面的原因。所有的质量管理权威都有一个共识:质量管理最大的一个障碍是质量改进中缺少上层主管的贡献。管理者的贡献意味着通过行动自上而下地沟通公司的想法,使所有员工和所有活动都集中于不断改进,这是一种实用的方法。只动嘴或公开演说不适合质量管理,管理者必须参与和质量管理有关的每一个方面工作并持续保持下去。在一项调查中70%的生产主管承认,他们的公司现在花费更多的时间在改进顾客满意的因素上。然而他们把这些责任授权给中层管理者,因而说不清楚这些努力成功与否。试想,这样的质量管理能够成功吗?
  四:没有目的的培训
  企业许多钱花费在质量管理的培训上,然而许多企业并没有因此得到根本的改进。因为太多的质量管理培训是无关紧要的。例如,员工们学习了控制图,但不知道在那里用,不久他们就忘记所学的了。可以说,没有目标、没有重点的培训实际上是一种浪费,这也是质量管理失败的一个因素。
  五:缺少成本和利益分析
  许多企业既不计算质量成本,也不计算改进项目的利益,即使计算质量成本的企业也经常只计算明显看得见的成本(如担保)和容易计算的成本(如培训费),而完全忽视了有关的主要成本,如销售损失和顾客离去的无形成本。有的企业没有计算质量改进所带来的潜在的利益。例如,不了解由于顾客离去而带来的潜在销售损失等。国外研究表明:不满意的顾客会把不满意告诉22个人,而满意的顾客只将满意告诉8个人。减少顾客离去率5%可以增加利润25%~95%,增加5%顾客保留可以增加利润35%~85%。
  六:组织结构不适宜
  组织结构、测量和报酬在质量管理培训、宣传中没有引起注意。如果企业还存在烦琐的官僚层次和封闭职能部门,无论多少质量管理的培训都是没有用的。在一些企业中,管理者的角色很不清楚,质量管理的责任常常被授给中层管理者,这导致了质量小组之间的权力争斗,质量小组缺少质量总体把握,结果是争论和混乱。扁平结构、放权、跨部门工作努力对质量管理的成功是必须的。成功的企业保持开放的沟通形式,发展了全过程的沟通,消除了部门间的障碍。研究表明:放权的跨部门的小组所取得的质量改进成果可以达到部门内的小组所取得成果的200%到600%。
  七:质量管理形成了自己的官僚机构
  在质量管理活动过程中,通常把质量管理授权于某质量特权人物。质量成为一个平行的过程,产生带有自己的规则,标准和报告人员的新的官僚层次和结构,无关的质量报告成为正常。这个质量特权人物逐渐张大渗透,成为花费巨大而没有结果的庞然大物。质量官僚们把自己同日常的生活隔离开来,不了解真实的情况,反而成为质量改进的障碍。
  八:缺少度量和错误的度量
  缺少度量和错误的度量是导致质量管理失败的另一个原因。不恰当地度量鼓励了短期行为而损失了长期的绩效,一个部门的改进以损失另一个部门为代价。例如,选择合适的价格改进了采购部门的绩效,但给生产部门带来了极大的质量问题。企业没有参考对比就如同猎手在黑夜里打猎物,其结果只是乱打一气,偶然有结果,更可能是巨大的损失。公司需要与质量改进有关的绩效度量手段,包括过程度量和结果度量。成功的公司都是以顾客为基础度量和监测质量改进的过程。
  九:报酬和承认不够
  战略目标、绩效度量和报酬或承认是支持企业质量改进的三大支柱。改变观念和模式转变需要具有重要意义的行为改变,行为在很大程度上受承认和报酬制度的影响。企业如何承认和回报员工是传递公司战略意图的主要部分。为使质量管理的努力富有成效,企业应当承认和回报 有良好绩效者,从而使质量改进成为现实。
  十:会计制度不完善
  现行的会计制度对质量管理的失败负有很大的责任。它歪曲了质量成本,没有搞清楚其潜在的影响。例如,与不良产品有关的成本如担保,甚至没有被看成是质量成本;废弃,返工被看成是企业的一般管理费。

posted @ 2014-12-03 13:42 顺其自然EVO 阅读(171) | 评论 (0)编辑 收藏

能存活19年的Bug不是Bug

 近日,各大网站,包括新浪、腾讯、网易、搜狐都报道了一则关于微软宣布修复了一个存在了19年的安全漏洞的新闻,以腾讯科技为例,它的标题是《微软修复已存在19年的漏洞》。对于一个软件制造界外的人来说,这是一个大快人心的消息,就跟一个隐藏了19年的纳粹分子终于被抓住的新闻一样轰动。但以程序员为职业的我,听到这样一个消息,却有一种非常不解、甚至是荒谬、搞笑的感觉。从软件生产的角度讲,如果一个bug能存活19年,那它还是个bug吗?
  一、很多项目生命期不超过19年
  我在很多国企开发过项目,这些项目几乎每过几年都会重新开发一回,老项目或者废弃、或者推倒重来,遇到领导换班子或上级政策方向的改变,更容易发生这种事情。事实上,有大量的软件存活不到19年,都很短命。这一方面是技术的原因,更重要的一方面是国情的因素。如果在这样的一个项目里有一个bug,当这个软件几年后被遗弃时,从来没有被人发现——更符合软件科学的说,没有给用户带来任何烦恼。这样的bug对于用户来说是不可见、不可知、根本不存在的。我们没有必要、也不应该将这样的bug称作bug,更不应该为这样的bug大惊小怪。
  二、修改bug有风险
  我记得有一个非常有趣的关于bug段子,说的是:
  代码中有99个小bug,
  99个小bug,
  修复了一个,
  现在,代码中的有117个小bug。
  虽然是个笑话,但作为程序员,我一点都笑不出来,因为这种事情在我们项目的开发过程中经常的会遇到。由于纠正接口中一个bug而导致其它程序调用这个接口时出现了另外的问题。你可能会嘲笑说这是测试程序写的不够周全,但很多时候,复杂的软件内部关联是很难让加班加点的程序员考虑周全的。
  所以,在一个复杂的软件里,特别是对于老项目,最早开发这个项目的人已经流失,而项目文档又写的不够清晰,如果一个bug不是特别严重、不影响核心业务,如果能说服客户不修改,那就优先考虑不修改,如果非要修改,那必须要深思熟虑、准备充足的测试用例,并想好回退方案,以防万一。
  三、是bug?还是设计的功能特征?
  之前就有一篇很好的文章指出,Bash里一个所谓的bug实际上是25年专门设计的功能,只是时过境迁,现在的使用环境发生了很大的变化,人们并没有及时的调整过去的老代码,或者现在的新环境并没有照顾过去的老接口。
  所以,我们今天看到的一个愚蠢的 bug,也许在历史上的某一天,是一个有意而为之的神奇特性。我们应该思考的不仅仅是这一刻的 bug 或者安全隐患本身,而是在软件开发这个极具创新的活动中,如何有效的保证某个特意设计的功能不会变成 bug。
  总之,一个19年的bug,一直默默无闻,没有被人发现、没有给用户带来麻烦、造成损失。我想,时间证明了这个bug是个善良的bug,是个好bug,我宁愿将它升级成一个功能。即使不能如此,使用用户在这些年的使用中也早就适应了这个bug,能够很好的与它和睦相处,已经不把它当成危险的敌人了。事实上,在用户的心里,它已经升级进化,蜕掉了bug的外壳。这样的bug,还是应该顺其自然,不改为好。程序员朋友们,你说呢?

posted @ 2014-12-03 13:41 顺其自然EVO 阅读(172) | 评论 (0)编辑 收藏

使用复杂度度量去改进软件质量

复杂度度量可以用来评估开发和测试活动,决定应该对哪里进行重构以提升质量和预防问题。在QA&Test 2014 conference 大会上,来自于英特尔的Shashi Katiyar就有效利用针对软件质量改进的复杂度度量提出了自己的见解。
  复杂度是一种不同的软件元素间交互的度量。按照Shashi的说法,软件复杂度直接反映了软件的质量和成本:如果代码复杂度比较高,那么这段代码的质量就会比较低,而且它的维护成本也会比较高。
  Shashi提出,如果软件产品中有复杂的代码,那么组织会面临以下的问题:
  较高的缺陷风险
  难以增加新的功能
  难以理解或维护这段代码
  难以验证
  你可以使用McCabe圈复杂度来度量复杂度。这种度量规定了代码中线性独立的路径条数,它反映了测试难度和软件的可靠性。它可以用来评估开发和维护工作量。
  基于复杂度数据,你掌握要覆盖所有路径最少需要多少测试用例。复杂度数据可以帮助你去:
  集中力量搞好复杂的模块
  找到最有效的测试技术
  了解停止测试的时机
  增加软件的可测试性
  Shashi解释说,你在软件系统的管理中做到更具可预测性:
  在任何软件产品开始工作之前,如果有人知道它是一个复杂的模块,那么就有可能在评估期为它赋予一些额外的时间。了解了复杂度能够预先帮助项目团队去进行评估,这种做法要胜过在开发和测试期去关注它,从而确保不会让产品的质量做出妥协。
  英特尔收集了复杂度度量和模块变更数量的数据。这些复杂度数据结合了客户记录的缺陷。如果一个模块是复杂的,并且由于缺陷进行了大量的变更,那么就决定去重构它。在重构之前他们确保有覆盖这些代码的测试用例。这种工作方式增加了重构的投资收益率。
  Shashi探讨了他所看到的软件开发复杂度与质量相关的挑战:
  在竞争激烈瞬息万变的环境中,公司通过为它的用户提供更多的特性来努力使它的服务有所不同。这就导致了大量的代码行和复杂度,这是个大挑战。如果未采用适当的预防措施去管理产品的复杂度,那么很快这些产品就将成为难以维护的产品。随着时间的推移,很多公司都不在使用老代码和老技术了,他们知道自己的系统太复杂了,把它们进行新技术的移植是一项极其复杂的任务。
  “在高复杂度的环境中,创新和开发高质量软件是极其重要的”Shashi说。“组织可以设定去减少所有高复杂度程序的复杂度,更加频繁地变更以改进他们软件的质量”。

posted @ 2014-12-03 13:41 顺其自然EVO 阅读(195) | 评论 (0)编辑 收藏

一个需求的奋斗史

一、需求中的相关术语简介
  1.马斯洛的需求层次理论
  生理需求、安全需求、社交需求、尊重需求、自我实现需求
  需求的本质是问题,问题的本质则是理想与现实的矛盾产生的差距
  2.产品经理素质之一:了解用户需求、理解用户心理
  3.用户vs客户:用户,使用产品的人;客户,够买产品的人、为产品付钱的人
  相比您的需求,我们可能更重视您的用户的需求。
  以用户为中心PK 以老板为中心的产品
  4.优先满足哪些用户需要和产品的商业目标要结合起来考虑。对于腾讯来说,用户数不可能再有爆发式增长,只能考虑从已有的用户身上挖掘用户价值,
  故需要优先满足的是核心用户的需求。对于刚创业的,没什么用户,需要添加些大众的功能,满足一般用户,让用户数增长起来。
  5.需求分析过程
  1>获取需求的方法:a.电话访谈;b.人物角色创建
  2>试着描述用户:通过功能反思其适用的用户人群
  3>用户研究:
  a.横向,用户的说和做。说表现了目标和观点,做反映了行为,用户的说和做往往是不一致的
  b.纵向,定性与定量。定性研究可找出原因,偏向于了解;而定量研究可发现现象,偏向于证实
  a和b结合。从定性到定量,再定性再定量,并且螺旋上升,而了解和证实也是不断迭代进化的。
  听用户定性的说(电话访谈)-->听用户定量地说(调查问卷)-->看用户定性地做(用户可用性功能测试)-->看用户定量地做(数据分析)
  二、需求管理
  导入:用户研究
  需求管理:需求采集-->需求分析-->需求筛选
  导出:需求开发
  需求采集包括:明确目标、选择采集方法、指定采集计划、执行采集、资料整理,
  三、需求采集
  1.用户访谈中常见问题与对策
  第一,用户说和做经常不一致的问题。解决方法,a.让用户在说的同时也做;b.区分用户说的事与观点,如我做了什么,步骤如何,碰倒什么问题,如何解决的。
  第二,样本少,以篇概全的问题。解决方法,a.随机抽样访谈;b.以增量方式进行访谈(先访谈n个,根据基本结论,再访谈n个,观察结论,并加大样本)。
  第三,用户过于强势,带我们上道。解决方法,时刻牢记访谈的目的。
  第四,我们过于强势,带用户上道。解决方法,牢记访谈目的,管好自己的嘴。
  访谈中的注意事项:
  A.避免一组固定的问题,准备好问题清单,并逐渐引导用户
  B.首先关注目标,任务其次。比用户行为更重要的是行为背后的原因,多问问用户为什么这么做。
  C.避免让用户成为设计师:听用户说,但不要照着做,用户的解决方案通常短浅、片面。
  D.避免讨论技术:对于略懂技术的用户,不要纠结于产品的实现方式。
  E.鼓励讲故事:故事是最好的帮助设计师理解用户的方法
  F.避免诱导性的问题:典型的诱导问题是如果有**功能,你会用吗?一般来说,用户会给出毫无意义的肯定答复。
 2.用户大会
  用户大会即邀请产品的用户到某一集中地点开会,可短时间内从多人处收集大量信息,为一种特别的用户访谈。
  1>明确目的:
  产品二期卖点确认、辅助运营决策;三期需求收集;现有产品用户体验改进等
  2>资源确定:
  A.时间:日期、几点、时长
  B.地点:场地、宣称用品、IT设备、礼物、食品饮料、桌椅
  C.人物:
  工作人员:分组分工,产品、运营、开发人员相互搭配,有冗余
  用户:确定目标用户、数据提取、预约,考虑人数弹性
  嘉宾:相关老板、合作部门的同事,不管是否来,提前发出邀请
  D.材料
  用户数据、产品介绍资料(测试环境的准备以及静态Demo备用)
  E.紧急备案
  用户大会前两天开一次确认会
  3>现场执行
  A.辅助工作:场地设置(轻松);引导/拍照/服务/机动;进场签到(送礼品);全程主持(进度控制);送客/收拾残局
  B.主流程
  产品介绍:买点介绍,与用户互动,观察用户更关注哪些功能,辅助上线前的运营决策,到底主推哪些卖点;
  抽取部分用户做焦点小组(主持人一对多的访谈),收集后续的需求,产品三期开始启动;
  抽取部分用户可用性测试,帮助产品二期做最后的微调
  合影留念
  4>结束以后
  资料整理:卖点总结报告、需求收集报告、可用性测试报告
  运营:本次活动在淘宝论坛发帖;内部邮件
  整个活动资料的整理归档,包括照片、各项资料/报告信息、用户数据等
  3.调查问卷
  用户访谈:提纲为开放式的,适用于心里比较疑惑的时候寻找产品方向,适合与较少的访谈对象进行深入的交流
  调查问卷:封闭式问题较多,适合大用户量的信息收集,但不够深入,只能获得某些明确问题的答案,不适合安排问答题。
  调查问卷常见问题:
  第一,样本的偏差,及样本与想了解的目标用户群体出现偏差。解决方法:A。尽可能覆盖目标群体中各种类型的用户,比如性别、年龄段、行业、收入等,保证各种类型用户的
  样本比例接近全体的比例; B.将目标群体的特征也定义成一系列问题,放入调查问卷中。
  第二,样本过少的问题,此时采用百分比没有意义。解决方法:至少获得大约100份的答案。
  第三,问卷内容的细节问题。解决方法:A.问题表述应无引导性,如使用你是否喜欢某个产品代替你喜欢某个产品吗?B.答案顺序打乱,对于陈述者用户趋向于选第一个或最后一个
  答案,对于一组数字,如价格和打分,则趋向于取中间位置。应该准备几种形式的问卷。或小范围试答后,根据反馈修改,再大面积投放。
  4.定性地做--可用性测试
  可用性测试目的:让实际用户使用产品或原型方法来发现界面设计中的可用性问题。
  方法:招募测试用户、准备测试任务、旁观用户操作的全过程、询问用户对于产品整体的主观看法或感觉以及当时如此做的原因,分析产生可用性问题列表,划分等级。
  可用性测试的常见问题与对策
  第一,如果可用性测试做得太晚(往往在产品要上线的时候),这是发现问题于事无补。解决方法,产品无任何型时,拿竞争对手的产品给用户做;产品有纸面原型时,
  拿着手绘的产品,加上纸笔给用户做;在产品只有页面demo时,拿demo给用户做;在产品可运行后,拿真实的产品给用户做。
  第二,总觉得可用性测试很专业,所以干脆不做。
  工作中需求采集还有以下方法
  单项需求卡片
  现场调查--AB测试
  日记研究--用户写的产产品应用体验
  卡片分类法--与用户一起设计既定产品需求下的模块分类

posted @ 2014-12-03 13:40 顺其自然EVO 阅读(189) | 评论 (0)编辑 收藏

基于SaltStack完成LVS的配置管理

 之前由于工作需求,编写了SaltStack的 LVS远程执行模块 , LVS service状态管理模块 及 LVS server状态管理模块 ,并 提交给了SaltStack官方 Loadblance(DR)及RealServer的配置管理.
  前置阅读
  LVS-DR模式配置详解 ,需要注意的是,LVS-DR方式工作在数据链路层,文中描述需要开启ip_forward,其实没有必要, 详情见 LVS DR模式原理剖析
  环境说明
  三台服务器用于LVS集群,其中主机名为lvs的担当的角色为loadblance,对应的IP地址为192.168.36.10;主机名为web-01和web-02的主机担当的角色为RealServer, 对应的IP地址分别为192.168.36.11及192.168.36.12
  LVS VIP: 192.168.36.33, Port: 80, VIP绑定在lvs的eth1口
  最最重要的是loadblance主机为Linux,并已安装ipvsadm, Windows/Unix等主机的同学请绕过吧,这不是我的错......
  开工
  Note
  以下所有操作均在Master上进行
  配置SaltStack LVS模块
  如果使用的Salt版本已经包含了lvs模块,请忽略本节内容,测试方法:
  salt 'lvs' cmd.run "python -c 'import salt.modules.lvs'"
  如果输出有 ImportError 字样,则表示模块没有安装,需要进行如下操作:
  test -d /srv/salt/_modules || mkdir /srv/salt/_modules
  test -d /srv/salt/_states || mkdir /srv/salt/_states
  wget https://raw.github.com/saltstack/salt/develop/salt/modules/lvs.py -O /srv/salt/_modules/lvs.py
  wget https://raw.github.com/saltstack/salt/develop/salt/states/lvs_service.py -O /srv/salt/_states/lvs_service.py
  wget https://raw.github.com/saltstack/salt/develop/salt/states/lvs_server.py -O /srv/salt/_states/lvs_server.py
  配置pillar
/srv/pillar/lvs/loadblance.sls
lvs-loadblance:
- name: lvstest
vip: 192.168.36.33
vip-nic: eth1
port: 80
protocol: tcp
scheduler: wlc
realservers:
- name: web-01
ip: 192.168.36.11
port: 80
packet_forward_method: dr
weight: 10
- name: web-02
ip: 192.168.36.12
port: 80
packet_forward_method: dr
weight: 30
/srv/pillar/lvs/realserver.sls
lvs-realserver:
- name: lvstest
vip: 192.168.36.33
/srv/pillar/top.sls
base:
'lvs':
- lvs.loadblance
'web-0*':
- lvs.realserver

posted @ 2014-12-03 13:40 顺其自然EVO 阅读(157) | 评论 (0)编辑 收藏

测试团队管理-第四篇:磨合与阵痛

 新测试部门在形式上成立了,面上的整合也都做了,深度的整合才刚刚开始。对已有部门人员和组织架构盘点一遍,发现情况极不乐观。第一,两名原代理测试主管,一名开始休为期四个月的产假,一名刚休四个月产假回来。在代理测试主管下面,有几名测试骨干,但是没人具备一点儿测试团队管理经验,过往基本上是每位 测试主管直接管理下面的二十位左右测试工程师,缺乏管理层次。第二,五分之一的部门员工正在孕期、产期或者哺乳期。第三,测试能力严重匮乏,以手工黑盒测 试为主,性能测试可以开展一点点,测试自动化和其他能力基本为零。第四,测试部门严重缺乏过程资产和技术积累,没有Bug总结分析,没有测试项目总结分析,没有测试分析设计,尚停留在测试执行层面,只有一些基本的测试计划、测试用例、测试报告和Bug库中的Bug。
  公司老板对原测试部门的不满积压太久,没有耐心再一点点等了,直接给出一剂猛药,立即执行,不容商量,那就是换血和淘汰。原测试主管不合格,先转为部门助理,后继安排再定,着手社招新测试主管;外部招聘加内部培养几名一线测试经理,搭建起部门的核心架构;对基层测试工程师,逐一评估,分批淘汰。这些命令对于刚熟悉新公司和部门基本情况的我,是发自内心不愿看到的,也是绝大多数人不愿看到的。
  在高层的重压下,在开发部门部门经理的疑虑中,在两名老测试主管的迷茫与彷徨中,在基层测试工程师的不解甚至背后议论中,招聘进新测试主管,提拔几名一线测试经理,走了一些测试工程师。外人看到的,是新来的部门经理冷血、部门人员动荡。我有时感觉自己好像站在一艘船上,这艘船四处漏水、在大海中颠簸飘摇; 而我就是就是这艘船的船长兼舵手,在东修西补的同时,还要尽力保持正确的航向高速行驶,不能翻船。
相关文章
测试团队管理-第三篇:部门整合(2)

posted @ 2014-12-03 13:39 顺其自然EVO 阅读(153) | 评论 (0)编辑 收藏

仅列出标题
共394页: First 上一页 6 7 8 9 10 11 12 13 14 下一页 Last 
<2024年4月>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

导航

统计

常用链接

留言簿(55)

随笔分类

随笔档案

文章分类

文章档案

搜索

最新评论

阅读排行榜

评论排行榜