phpunit在公司电脑上慢的原因

为什么PHPUnit在我公司上的电脑测试失败非常慢?如果这是一个常见问题,我想作者早就已经改过了,我想是出于公司电脑的特殊性,公司电脑一共装了两个特殊的软件,一个是监控上网的,一个是隔离工作区和正常使用区。第二个软件的两个区是不可以随意通信的软件名称SDC,在隔离的工作只有少数的软件可以上网,而且对文件系统的操作都是有监控的,文件不能外发,我猜想每次操作它都监控了,这样导致PHPUnit在扫描它本身的文件时非常慢,巨慢无比。在我的Arch上,是秒杀的。刚开始时什么也不知道的,想用xhprof去找出时间都消耗在哪里,可惜我找的是一个PHPUnit的xhprof扩展,它不测试PHPUnit本身的性能。只能自己一个一个通过die(‘fuck’)去跟踪代码(比较笨)。

最终我找到了PHPUnit/Util/GlobalState.php文件,有一个函数phpunitFiles,查找PHPUnit框架自身框架的文件名,并添加到静态数组中,用处是在测试失败时,过滤丢出异常里的PHPUnit框架文件路径,从而只显示被测试函数的文件路径及行号。优化的第一步,我自然的想到了缓存,于是我把这个数组缓存起来了,从缓存中读取文件列表。

    /**
     * @return array
     * @since  Method available since Release 3.6.0
     */

    public static function phpunitFiles()
    {
        $cachefile = sys_get_temp_dir() . '/phpunit.cache' ;
        if (!file_exists($cachefile) ) {
            if (self::$phpunitFiles === NULL) {
                self::addDirectoryContainingClassToPHPUnitFilesList('File_Iterator');
                self::addDirectoryContainingClassToPHPUnitFilesList('PHP_CodeCoverage');
                self::addDirectoryContainingClassToPHPUnitFilesList('PHP_Invoker');
                self::addDirectoryContainingClassToPHPUnitFilesList('PHP_Timer');
                self::addDirectoryContainingClassToPHPUnitFilesList('PHP_Token');
                self::addDirectoryContainingClassToPHPUnitFilesList('PHPUnit_Framework_TestCase', 2);
                self::addDirectoryContainingClassToPHPUnitFilesList('PHPUnit_Extensions_Database_TestCase', 2);
                self::addDirectoryContainingClassToPHPUnitFilesList('PHPUnit_Framework_MockObject_Generator', 2);
                self::addDirectoryContainingClassToPHPUnitFilesList('PHPUnit_Extensions_SeleniumTestCase', 2);
                self::addDirectoryContainingClassToPHPUnitFilesList('PHPUnit_Extensions_Story_TestCase', 2);
                self::addDirectoryContainingClassToPHPUnitFilesList('Text_Template');
            }
            file_put_contents($cachefile,serialize(self::$phpunitFiles));
        }else{
            self::$phpunitFiles = unserialize(file_get_contents($cachefile));
        }

        return self::$phpunitFiles;
    }
这不是PHPUnit的问题,而是我该死的。。you know..

自动化单元测试的好处

我并没有在实际项目中或某个功能开发中高覆盖率的使用过单元测试,但我对它确有很强的好感,在未来的中型以上项目开发我会尽可能的写单元测试。我认为的单元测试存在以下几个好处:

快速定位错误

开发过程或者重构中,为了需求的变化、BUG修正,我们去修改一下代码,当项目比较小时或者项目大但代码耦合度比较高时(功能间互相代码插入,个人理解),我们修改的代码非常有可能引起另外多个BUG,纵使我们知道有可能引起新的问题,我们能怎么做呢 ?到项目的每一个角落测试一下吗?太消耗时间了,我们也没有那个耐心去这样测试,如果使用了自动化的框架,并且我们写了质量高覆盖可观的单元测试,我们可以在每一小处修正之后运行自动化的单元测试,出错的地方自然就会显示出来,当然不保证没有其它的错误,但会找出大部分的错误。

省去大量的人工操作

这两天在公司没事做,一直为另外一个同事做测试,我不喜欢这样的测试,情况大至是这样,同事做的是一个调班的功能,它与个人工作日相关(一般情况下周一至周五是工作日,存在特殊人特殊情况的会自己的个人工作日),需要测试的地方不少,比如简单的调班是否能成功,取消调班是否成功,自己与自己调班能否成功,稍微复杂一点的就是调班后,上级审核通过了,又改变这个调班,与另外一个人调班(逻辑上是取消上次调班,重新调班)。我做了两天的测试,第一天测试出的BUG交于同事修正,第二天同事已经改完BUG,我再测试。测试的内容就是上述的几天测试用例,反复的操作,我调班调了100多次,可能有点夸张,我都测试挺多遍的。我想如果我将这几种测试用例写在单元测试中,我只需要运行一下单元测试,每次同事修改完后同事只要自己运行一下单元测试,就可以了,也就没有我什么事了。

提高自己的编程能力

因为自己的单元测试的实践比较少,这里的体会不会太深,只是说说自己的猜想。在TDD里,先写单元测试,这时会考虑自己要写一样有什么接口,能接受多少种情况的参数,这个目前不适合我,我现在适合后写单元测试,在写完一个函数后,写单元测试时,同样也需要使用多组数据测试,这样写得多了,我想在没有单元测试的情况下,我们也会考虑的全面。

重复一下牛人们的观点,单元测试的目的是为了提高代码质量,节省自己的时间,之前我有想过追求单元测试高覆盖率和满足自己怪心理地写单元测试,我错了。。

递归-进制转换

今天也去学习了一下递归,我比较害怕递归,它的思想对我来深不可测,妙得没话说。但还是需要面对 ,学会使用它!

递归的用法大至是将大的,麻烦的问题简化成小问题,然后组合起来解决一个大问题,之前在快速排序时也有用到递归,当时觉得它一个正向分解大问题,而斐波那契数列是为得到第N个数就得知道前两个数的值,可能属于传说中的尾递归吧,从尾部开始返回,每返回一次解决一个小问题。十进制转二进制中,也像是尾递归。进制转换规律是这样的:十进制数整除2后余2,余数倒序输出,

代码:

 

#include <stdio.h>

void convert(int num ){
    if ( num/2 ==0 ) {
        printf("%d",num%2);
        return ;
    }
    convert(num/2);
    printf("%d",num%2);
}
int main(int argc, const char *argv[])
{
    int number ;
    while(1){
        scanf("%d",&number);
        if (number == 0) {
            return 0;
        }
        convert(number);
        printf("\n");
    }
    return 0;
}

递归需要一个终止条件,就是整除后等于0,打印余数,开始返回,当达到终止条件时,程序像是往回走,解决所有的小问题。
文章写得不好,代码可运行

排序-插入排序

插入排序应该算是比较容易理解的排序,与冒泡排序差不多。

插入排序是保证前P+1个元素(因为数组索引值都是从0开始,P其实是索引值)是已排序的,然后不断的移动P游标,将符合条件的插入到排序的正确位置,P这个游标从索引1开始,与前一个数进行比较,拿升序排序来说,如果0上的元素大1上的元素互换两处的值,这样保证了前两个元素(P+1)个元素是已经排序的,再进行第二次排序时,P游标向右移动一个位置,放到2上,将2上的元素值与2之前的元素比较(比较的顺序是从右到左,因为在P之前的元素是已排序的),依次比较过去,如果有一个前元素比P上元素的值大,那么互换两个元素的值。代码:

#include <stdio.h>
#include <time.h>

#define LEN 100

void insertsort(int a[]){
    for (int i = 1; i < LEN; i++) {
        int tmp = a[i];
        int p =0;
        for (p = i; p>0 && a[p-1] > tmp; p--) {
            a[p] = a[p-1];
        }
        a[p] = tmp;
    }
}

int main(int argc, const char *argv[])
{
    int a[LEN];

    srand((int)time(0));
    for (int x = 0; x < LEN; x++) {
        a[x] = rand() % 1000;
    }
    insertsort(a);
    for (int i = 0; i < LEN; i++) {
        printf("%d ",a[i]);
    }
    printf("\n");
    return 0;
}

排序-快速排序

算法这东西如果没有经常遇到或者自己经常的练习总是会忘记,到了需要用的时候已经不会了。不知道想出这些算法的是怎么想出来的,很难理解。今天学习了简单的快速排序,来记录一下。这个排序我觉得包含了两部分,一部分是基数两边的调整,一部分是以两边游标的汇合点把数组分成两个部分,然后递归,递归的终止条件是最后一边的元素个数小于2个。

首先选择一个基数,这里我选择的基数与大部分程序一样,选择了“等排序元素列表”中的第一个元素存放在key,然后分别建两个头和尾元素的游标l和r,最开始,r上的值与基数key做比较,如果r上的值大于key那个r减1,也就是右游标向左移动一个位置 ,当r的值小于key时,那么把r上的值复制 到l元素上,并且停止r上的值的进行比较,并且把l这个左游标向右移动一个位置 l++,就正如网上博客上说的r现在是一个被挖掉的坑,当做是空的,没有值(实际有值)。下一步转向左游标处(l)的移动,判断l上的值是否小于key,如果是那么游标向右移动一个位置,如果不是那么把l上的值复制 到r上,r原本是被拿掉值的一个坑,就这样完成一次调整,因为元素个数和一些随机情况,就这样一次调整是无法排序成功的,l和r之间还有等调整的元素,所以这一段的外围需要一个循环(while(l<r))保持l和r不断的靠拢直到相等,在相等的地方这个值一定是一个坑,至少是l挖的还r挖的就不得而知了,然后把key值放入这个坑,以上的目的是把小于key和大于key的数分到两边,接下来是以这个l==r的位置分出两部分一大一行再进行以上的调整,只到left的值不再小于l-1或者 right的值不再大于r+1了,函数退出,数组排序完成。个人觉得这个递归与平时自己遇到的递归不太一样,它是正着走的,不依赖于下一次的递归函数调用,而平时的递归是依赖于下一次的递归调用的,如果是平时的遇到那种递归反而容易理解一点。总体的感觉是起先粗糙的以基数为中心把小的放左边大的放右边,然后再这样分下去直到比较具体的:只有两个数的时候。记录完毕。对了,看着维基百科时对i++那里有点不理解,所以顺便做一个i++和++i的试验在代码中,平常这些运算符单独一行使用是没有问题的,如果与其它东西结合起来就要小心点了,怕是找不出BUG所以。代码如下:

 

#include <stdio.h>
#define LEN 4
void my_sort(int a[],int left,int right);
int main(int argc, const char *argv[])
{
    /* int a[LEN]={9,46,89,1,50,11,65,43,8,7}; */
    int a[LEN]={4,3,2,1};
    my_sort(a,0,LEN-1);
    int x = 0;
    printf("%d...",a[x]);//这里是在试验x++与++x的区别
    printf("%d...",a[x++]);
    printf("%d...",a[++x]);
    for (int i = 0; i < LEN; i++) {
        printf("%d ",a[i]);
    }
    return 0;
}
void my_sort(int array[],int left,int right){
    int l = left,r = right;//定义两个游标
    int key = array[left];//定义一个基准的,相当于一个分割线
    while(l<r){
        while (l<r && array[r] > key ){//移动边的游标,当游标处的值小于基准值时,
                                    //把当前游标值放到左边游标所处位置,
                                    //并把左边的游标往向移动一个位置
            r--;
        }
        if ( l < r ) {
            array[l] = array[r];
            l++;
        }
        while (l<r && array[l] < key){//开始移动左边的游标,经过上面一次循环后,
                                //游标已经向右移动一个位置,比较当前游标
                                //与基准值的大小,如果当前值大于游标处的
                                //值大于基准值,那么把当前值放到上个循环
                                //的位置,并把右边的游标向左移动一个位置
            l++;
        }
        if (l<r) {
            array[r] = array[l];
            r--;
        }
    }         //最外层的循环是让这一次的整体交换完成
            //两边不断交换直到两个游戏在同一个位置
            //然后以这一位置进行分治,划分为左边和右边
    array[l] = key;
    if (left<l-1) {
        my_sort(array,left,l-1);
    }
    if (l+1<right) {
        my_sort(array,l+1,right);
    }
}

指针与双向链表

我是菜鸟,给自己找了一个借口,”学习一些开源软件原理不懂数据结构,不能知道了解其中的原理”,所以终于开始学习一下数据结构了.从PHP的思维突然转到指针上来有一些不适应,这导致我在练习双向链表的时候出一个问题,循环输出链表的的一段元素. 函数的功能是在pos元素之前插入ele元素,看了很多次这个函数没有大问题:20130424085442

 

问题是出在了调用上:20130424085335

我把已经使用过一次的ele做为参数传入到了insertElement函数中,前一次操作是把ele添加入链表的尾部,ele指针的值 没有变化,再在insertElement函数中使用这个参数,会把链表的最后一个也就是ele的pre和next指针指向pos的前驱和后继,理论是把ele元素拖到了链表需要插入的指定位置了,但是在appendElement之前的链表的next还是指向ele的,那么在循环输出时,以cursor->next != NULL 作为while的终止条件时就会出现循环,当访问到了链表理论上的最后一个的next时,都会回到被插入元素位置,继续输出.
在这个问题上我把指针的意义理解得很糊涂,指针在声明后未初始化是不可以使用的,初始化的方法有很多种,直接分配内存空间,同类型的指针赋值,在这个例子中,我也出现了指针未初始化的错误,指针不会自动初始化为NULL,我想初始化这个事情要做好,并且指针在使用完毕后也要NULL.容易指出毛病,找不出问题所以.

Cygwin:i686-pc-cygwin/bin/ld: cannot find -luser32

Cygwin 安装了Gcc,来编译一下helloworld.c的小程序,出现:

/usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../../i686-pc-cygwin/bin/ld: cannot find -luser32
/usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../../i686-pc-cygwin/bin/ld: cannot find -lkernel32
/usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../../i686-pc-cygwin/bin/ld: cannot find -ladvapi32
/usr/lib/gcc/i686-pc-cygwin/4.5.3/../../../../i686-pc-cygwin/bin/ld: cannot find -lshell32

解决办法是:

find / -name *user32*
ln -sf /usr/i686-pc-mingw32/sys-root/mingw/lib/libshell32.a /usr/lib/

参考:
http://stackoverflow.com/questions/4272139/gcc-cygwin-compile-error
 

献给女人的JavaScript计算器

最近女人在学习js,但她觉得找不到方向,不清楚自己的水平是怎么样的,我的意见是多练习,写写小插件,小模块什么的,在学习过程里总结,我建议她写一个简单的计算器,当做练习,不要再去别人的代码了,做自己想做的东西思路问题非常清晰的。她觉得计算器超出了她的水平,我倒觉得挺简单的,她让我自己写一个,我就去试着写一个,写了小半天,才写出来,看来,我依然是程序菜鸟。

在写这个计算器时,我只想到了两种办法,一种是把全部的输入输入完整直到输入=号时,计算结果,就是分析1+2+3+4 这个式子的结果,想想这个写起来麻烦,个人算法能力太弱,第二种是每输入一个算术运算符时,计算前一个式子的结果,初始化一个result的变量做为结果的存储,初值为0,初始化一个optr变量做为算术运算符的存储,初始值为+号,还差一个pre这个在算术运算符之前数的存储,当我们输入10再输入一个加号时,运算前一个算式的值,也就是运算0+10的值,再存储输入10后的那个+号到optr中,然后我们再输入10,然后不管输入运算符还是=号都会计算前一个算式,也就是10+10,再判断如果是=号的话把结果显示在文本框中,如果不是那再接着如些计算。我就是写了这第二种的。在写的过程中,我尝试着不去调试,在本子还脑海里构思后,再写代码,最后经过两次的调试居然就写成了。这心里舒服的,我想就应该这样写程序。不知道这个小程序对于女人来说是否难度高,女人声称要学javascript,暂时不学jquery,我双手赞成,不过后期还是要用的。。附小代码:

<html>
    <head>
        <meta http-equiv="content-type" content="text/html; charset=utf-8">
   
        <title>Demo</title>
        <style type="text/css" media="screen">
            #btns{
                width:150px;
            }
            #btns>input{
                width:30px;
            }
        </style>
       
    </head>
    <body>
       
        <div id="panel">
            <input type="text" id="textArea" name="" value="">
            <div id="btns">
                <input type="button" name="some_name" onclick="Press(this)" value="0">
                <input type="button" name="some_name" onclick="Press(this)" value="1">
                <input type="button" name="some_name" onclick="Press(this)" value="2">
                <input class="operator" type="button" name="some_name" onclick="Press(this)" value="+">
                <input type="button" name="some_name" onclick="Press(this)" value="3">
                <input type="button" name="some_name" onclick="Press(this)" value="4">
                <input type="button" name="some_name" onclick="Press(this)" value="5">
                <input class="operator" type="button" name="some_name" onclick="Press(this)" value="-">
                <input type="button" name="some_name" onclick="Press(this)" value="6">
                <input type="button" name="some_name" onclick="Press(this)" value="7">
                <input type="button" name="some_name" onclick="Press(this)" value="8">
                <input class="operator" type="button" name="some_name" onclick="Press(this)" value="*">
                <input type="button" name="some_name" onclick="Press(this)" value="9">
                <input type="button" name="some_name" onclick="Press(this)" value="C">
                <input class="operator" type="button" name="some_name" onclick="Press(this)" value="/">
                <input class="operator" type="button" name="some_name" onclick="Press(this)" value="=">
            </div>
        </div>
        <script type="text/javascript" charset="utf-8">
            var result = 0;//记录上一次计算的结果,这个计算器是每按一次算术符号计算一次,保存到这个变量中的
            var pre = 0;//保存按下算术符号时文本框中的值
            var optr = '+';//按下算术符号时,保存这个算术符号
            var clear = false;//清除是否文本框时的状态
            function Press(self){

                //获取文本框
                var textArea = document.getElementById('textArea');
                //如果是等号的号那么计算文本框中的算术的值
                if(self.value == 'C'){//如果是C的话,就清除文本框,把用来做状态的变量重置一下
                    textArea.value = '';
                    result = '0';
                    pre = '0';
                    optr = '+';

                }else{//最后如果不是C的话

                    if (self.className == 'operator') {//我用类名来标记算术符号,这里判断如果是+-*/符号的话
                        result = cal(optr,result,textArea.value);//计算前一个算术,当第一次如果时,我默认了optr=+号,result=0,所以第一次计算是0+(输入的值)
                        optr = self.value;//保存当前的算术符号到optr这个变量中
                        if (self.value=='=') {//如果是=号的话,把结果放到文本框中
                            textArea.value = result;
                            result = '0';//结果变量清0差不多回到原始状态,就一点的不同,就是原始状态里文本框是没有内容的
                            optr = '+';//原始状态里的算术符号
                        }
                        clear = true;//按下操作符的就标记clear为true,来告诉下一个流程,我们要清除文本框里的内容
                    }else{//如果不是算术操作符号的话,
                        if (clear) {//如果需要清除文本框 的话,那么清除文本框,并且clear=false告诉下一个流程,我已经清除过文本框了,不需要再清除
                            textArea.value = '';
                            clear =false;
                        }
                        textArea.value += self.value;//以上条件都符合的话把数字添加到文本框 中
                    }
                }
            }

            //计算函数,opt表示算术符号,r表示以前的计算结果,c表示当前输入算术符号之前的值
            function cal(opt,r,c){
                r = parseFloat(r);//把r这个结果值转化为可以带小数的值
                c = parseFloat(c);//当前值 也转化为可以带小数的值
                switch (opt) {//按算术符号来计算,是加号的做加运算,其他也一样
                    case '+':
                        r = r + c;
                        break;
                    case '-':
                        r = r - c;
                        break;
                    case '*':
                        r = r * c;
                        break;
                    default:
                        r = r / c;
                        break;
                }
                return r;
            }
        </script>
    </body>

</html>

保护NERDTree的窗口不被其它窗口替换

最近用了一下 minibufexplorer ,感觉 它比tab好用点,但存在一个个人误操作的问题,在NERDTree的窗口使用Ctrl+Right(自己绑定切换Buffer的快捷键)时,NERDTree窗口会被替换,经过几番搜索得

autocmd FileType nerdtree noremap <buffer> <C-Right> <nop>
autocmd FileType nerdtree noremap <buffer> <C-Left> <nop>

大致的意思是如果文件是nerdtree 那么匹配正常模式下的buffer类命令 Ctrl+Right 不产生任何影响.
参考 http://stackoverflow.com/questions/10216917/prevent-certain-command-mappings-while-focused-on-nerdtree

 

写不出逻辑的一天

今天是一个需要反思的日子,今天工作到快下班时候,写不出一个比较简单的逻辑,脑子比较混乱,后来觉得自己实在写不出来了,请教自己领导,看着他不调试的写着代码,将完整一段HTML拆分开,再按着他脑子里的逻辑把程序就这样写了出来.这点很佩服他,估计这也是他为什么成为领导的一个原因吧.

我呢是一个时刻会调试程序的程序员,这也算是一种 不太合格的表现,我时刻在调试的原因就是自己的逻辑一点也不清晰,总是想到一点写一点,写到一半写一半,反复的操作把一个函数 一个功能”拼”出来.如果一直这样下去我想我一定不会有什么出息.

人在紧张的情况下一般是干不好事情的,也许只有我个是这个样子,今天的状态不对除了自己有点累犯迷糊外就是女人打了好几个电话过来,让我有迫切的心想早点回去,所以架着自己努力去完成,并且这个项目也快交付了,这样情形下我的脑子到底在想什么也不知道了.

古人说凡事以平常心对待,差不多也能在我这件事情上起点作用吧