jmeter组件介绍

测试计划:描述一个性能测试,包含本次测试所有相关功能
线程:通常使用thread group,一个线程组看成是一个虚拟用户组.每个线程是一个虚拟用户
测试片段:当它是一个模块控制器或者被控制器所引用时才会被执行
控制器:{ 两种控制器,取样器(sampler)和逻辑控制器(logic controller)

取样器(Sampler)是性能测试中向服务器发送请求,记录响应信息,记录响应时间的最小单元
逻辑控制器:
一类是控制Test Plan中Sampler节点发送请求的逻辑顺序控制器
另一类是用来组织和控制Sampler节点的 }
监听器:
对测试结果进行处理和可视化展示的一系列组件,常用的有图形结果、查看结果树、聚合报告等
以上的五类原件就可以构成一个简单的性能测试脚本
jmeter提供的其他组件:
配置原件:用于提供对静态数据配置的支持
定时器:用于操作之间设置等待时间,等待时间使性能测试中常用的控制客户端QPS的手段
断言:用于检查测试中得到的响应数据等是否符合预期,Assertions一般用来设置检查点,
用以保证性能测试过程中的数据交互与预期一致

4144620615

3.参数(args,kwargs)作用
这个参数使用在不知道会传入多少个参数的情况下,首先,解释星号的作用,一个星号*的作用是将tuple或者list中的元素进行unpack,分开传入,作为多个参数;两个星号
的作用是把dict类型的数据作为参数传入。
注意:在函数的参数同时有
args和kwargs时,args的使用必须要在kwargs的前面,否则编译器会报错。
eg:def fun(*args,
kwargs):
print("args = ",args)
print("kwargs = ",kwargs)
fun(a=1,b=2,c=3,d=4)
输出:args=()
kwargs={'a':1,'b':2,'c':3}
4.python多线程(其实不是真正的多线程GIL锁)
1)python实现多线程是使用的threading模块
threading 模块提供的常用方法:
 threading.currentThread(): 返回当前的线程变量。
 threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
 threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
2)简单线程的创建:
简介线程的创建,先定义执行任务的函数,然后调用threading.Thread(target=say_hello, args=('one',))方法即可,
启动线程使用start()方法,线程等待执行结束使用join()方法。
eg:
def main():
# 定义任务处理函数
say_hello = lambda x: print("%s say hello" % x)
# 创建线程,args是传递的参数
thread_1 = threading.Thread(target=say_hello, args=('one',))
# 启动线程
thread_1.start()
# 等待线程执行完成
thread_1.join()
if name == 'main':
main()
3)创建线程的另一种方式,定义线程类,线程类需继承threading.Thread,同时定义 init方法,用于传递的初始化参数,另外定义run(self)方法,用于线程执行的操作
import threading
class sayHelloThread(threading.Thread):
# 指定线程需要的参数name
def init(self, name):
threading.Thread.init(self)
self.name = name

# 指定线程运行时的操作
def run(self):
    print('%s say hello' % self.name)

def main():
# 创建线程
thread_1 = sayHelloThread('one')
# 启动线程
thread_1.start()
# 等待线程执行完成
thread_1.join()
if name =='main':
main()
(4)线程池的创建:创建线程池使用threadpool.ThreadPool(n),n表示线程池内的线程个数。
然后创建线程池要执行的任务,使用makeRequests(callable_, args_list, callback=None,exc_callback=_handle_thread_exception)方法,
包含执行的方法和参数列表。
线程的执行使用putRequest(self, request, block=True, timeout=None)方法。
等待线程池执行结束用wait()方法。
eg:def say_hello(name):
print('%s say hello' % str(name))
def main():
# 创建线程池,指定线程数为4
pool = threadpool.ThreadPool(4)
# 指定任务参数列表,里面一个元素代表着一个任务需要的参数
task_param_list = ['one', 'two', 'three', ['four_1', 'four_2']]
# 创建任务列表
task_list = threadpool.makeRequests(say_hello, task_param_list)
# 任务在线程池中执行
[pool.putRequest(x) for x in task_list]
# 等待任务执行完成
pool.wait()
print("dispose finished")
if name == 'main':
main()
(5)线程锁:
这里介绍下用于多线程同步的锁,threading.Lock()用于创建锁,lock.acquire()获取锁,lock.release()释放锁。
eg:import threading

创建锁

lock = threading.Lock()

创建线程类

class sayHelloThread(threading.Thread):
# 指定线程需要的参数
def init(self, name):
threading.Thread.init(self)
self.name = name
# 指定线程运行时的操作
def run(self):
# 获取锁
lock.acquire()
print('%s say hello' % self.name)
# 释放锁
lock.release()
def main():
# 创建线程
thread_1 = sayHelloThread('one')
thread_2 = sayHelloThread('two')
# 启动线程
thread_1.start()
thread_2.start()
# 等待线程执行完成
thread_1.join()
thread_2.join()
if name == 'main':
main()
(6)解决多线程之间数据完整性和状态同步的最简单方法自然就是加锁。
a.GIL锁Global Interpreter Lock,全局解释器锁
它的作用就是在多线程情况下,保护共享资源,为了不让多个线程同时操作共享资源,导致不可预期的结果而加上的锁,在一个线程操作共享资源时,其他线程请求该资源,只能等待GIL解锁。
b.线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据
c.虽然Python解释器可以运行多个线程,但只有一个线程在解释器中运行。 python 每执行100个字节码,GIL锁就会解锁一次,让其它线程执行,所以,python多线程环境,是交替执行,上下文切换,并没有同一时刻执行代码.
对于任何Python程序,不管有多少的处理器,任何时候都总是只有一个线程在执行(这点上Python是不同于Java的,对于多核的情况,Java可以同时开启多个线程进行处理 。Python多进程库multiprocessing

5.python的垃圾回收制
1)Python的GC模块主要运用了引用计数来跟踪和回收垃圾。在引用计数的基础上,还可以通过“标记-清除”解决容器对象可能产生的循环引用的问题。通过分代回收以空间换取时间进一步提高垃圾回收的效率。
2)引用计数:当一个对象的引用被创建或者复制时,对象的引用计数加1;当一个对象的引用被销毁时,对象的引用计数减1,当对象的引用计数减少为0时,就意味着对象已经再没有被使用了,可以将其内存释放掉
优点:即实时性任何内存,一旦没有指向它的引用,会被立即回收.
缺点:循环引用,因为对象之间相互引用,每个对象的引用都不会为0,所以这些对象所占用的内存始终都不会被释放掉
(3)标记清除:
标记-清除只关注那些可能会产生循环引用的对象
寻找跟对象(root object)的集合作为垃圾检测动作的起点,从root object集合出发,沿着root object集合中的每一个引用,如果能够到达某个对象,则说明这个对象是可达的,那么就不会被删除
当检测阶段结束以后,所有的对象就分成可达和不可达两部分,
(4)分代回收:
Python默认定义了三代对象集合,垃圾收集的频率随着“代”的存活时间的增大而减小。也就是说,活得越长的对象,就越不可能是垃圾,就应该减少对它的垃圾收集频率。
6.内存泄露与内存溢出
内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory
内存泄露 memory leak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,但内存泄露堆积后果很严重,无论多少内存,迟早会被占光。
7.python是如何进行内存管理的?(内存池机制的概念)
引用计数,垃圾回收,内存池机制
引用计数:Python内部记录了对象有多少个引用,即引用计数,当对象被创建时就创建了一个引用计数,当对象不再需要时,这个对象的引用计数为0时,它被垃圾回收。
垃圾回收:当内存中有不再使用的部分时,垃圾收集器就会把他们清理掉。它会去检查那些引用计数为0的对象,然后清除其在内存的空间。当然除了引用计数为0的会被清除,还有一种情况也会被垃圾收集器清掉:当两个对象相互引用时,他们本身其他的引用已经为0了。
内存池机制:Python中所有小于256个字节的对象都使用pymalloc实现的分配器,而大的对象则使用系统的 malloc。

8.python如何复制一个对象,分为浅拷贝和深拷贝
copy.copy()浅拷贝
copy.deepcopy()深拷贝
浅拷贝:拷贝了最外围的对象本身,内部的元素都只是拷贝了一个引用而已。也就是,把对象复制一遍,但是该对象中引用的其他对象我不复制
深拷贝:外围和内部元素都进行了拷贝对象本身,而不是引用。也就是,把对象复制一遍,并且该对象中引用的其他对象我也复制
9.如何用python来查询替换一个文本字符串
可以使用sub()方法来进行查询和替换,sub方法的格式为:sub(replacement, string[, count=0])
replacement是被替换成的文本
string是需要被替换的文本
count是一个可选参数,指最大被替换的数量
subn()方法执行的效果跟sub()一样,不过它会返回一个二维数组,包括替换后的新的字符串和总共替换的数量
10.python的发送和接收邮件?
发送邮件:(SMTP协议)
SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式。SMTP协议属于TCP/IP协议簇,它帮助每台计算机在发送或中转信件时找到下一个目的地.
python中的SMTP模块:
接收邮件:
POP3和IMAP:POP是指邮局协议,目的是让用户可以访问邮箱服务器中的邮件;后来又出现了IMAP协议(Interactive Mail Access Protocol),即交互式邮件访问协议.
python的poplib模块支持POP3.
11.函数编程里的闭包函数
外函数装饰内函数。外函数申请的变量内函数里使用,外函数返回内函数的引用。
12.python2.x与python3.x的根本区别以及具体一点的区别
Py3.X源码文件默认使用utf-8编码;
依赖包的名称变化很大;

13.你知道python在java环境里用什么编译器执行么?
所需要的软件以及插件:Eclipse 64位、JDK_8u131_64位、python2.7.8、PyDev5.7.0插件
14.为什么python的执行速度慢,但是科学计算与神经网络还是用python。
Python 比较慢,但其牺牲性能可以提升工作效率
15.你知道python有哪些变体么?
cython:从语法层面上来讲是Python语法和C语言语法的混血

16.python是编译执行还是解释执行(解释型语言也可以编译)
但是 Python(这里主要是指CPython)并不是严格的解释型语言,因为 Python 代码在运行前,会先编译(翻译)成中间代码,每个 .py 文件将被换转成 .pyc 文件,.pyc 就是一种字节码文件.所以,解释型语言其实也有编译过程,只不过这个编译过程并不是直接生成目标代码,而是中间代码(字节码),然后再通过虚拟机来逐行解释执行字节码。
17.深搜与广搜的区别?深搜用栈还是队列?
深度优先搜索所遵循的搜索策略是尽可能“深”地搜索树。它的基本思想是:为了求得问题的解,先选择某一种可能情况向前(子结点)探索,在探索过程中,一旦发现原来的选择不符合要求,就回溯至父亲结点重新选择另一结点,继续向前探索,如此反复进行,直至求得最优解。深度优先搜索的实现方式可以采用递归或者栈来实现
深搜合适找出所有方案,广搜则用来找出最佳方案
深度优先搜索的搜索顺序就是每次选父节点中的一个子节点进行搜索不断下移,直到子树为空后回溯
广度优先搜索的搜索顺序就是每个父亲节点的子节点遍历完才遍历新的节点.
广搜则是操作了队列.

python入门(二)

list/tuple/dict

1)list列表(python的苦力)
创建list列表:lists=[1,2,3,4,5]
添加新元素:lists.append()在list的末尾添加一个元素
lists.insert(x,y):x代表下标,y代表要添加的数
lists.extend(list2)合并两个list,list2中仍然有元素
查看列表中的值: print(lists)遍历列表
print(lists[i])查到具体的某个
print(lists.count(xx))查看某个元素在这个列表里的个数,如果改元素不存在,那么返回0
print(lists.index(xx))找到这个元素的小标,如果有多个,返回第一个,如果找一个不存在的元素会报错
删除list中的元素: list.pop() 删最后一个元素
list.pop(n)指定下标,删除指定的元素,如果删除一个不存在的元素会报错
list.remove(xx) 删除list 里面的一个元素,有多个相同的元素,删除第一个
print(list.pop()) 有返回值
print(list.remove()) 无返回值
del list[n] 删除指定下标对应的元素
del list 删除整个列表, list删除后无法访问

排序与反转:list.reverse()将列表反转
list.sort()排序,默认升序
list.sort(reverse=True) 降序排列

列表操作的函数:
1、len(list):列表元素个数
2、max(list):返回列表元素最大值
3、min(list):返回列表元素最小值
4、list(seq):将元组转换为列表
5、enumerate 用法(打印元素对应的下标):
eg:list1=['a','b','c','d']
for i,v in enumerate(list1):
print('index is %s,value is %s'%(i,v))
list的循环与切片:
循环:for i in list
切片:name[n:m] 切片是不包含后面那个元素的值(顾头不顾尾)
name[:m] 如果切片前面一个值缺省的话,从开头开始取
name[n:] 如果切片后面的值缺省的话,取到末尾
name[:] 如果全部缺省,取全部
name[n:m:s] s:步长 隔多少个元素取一次
2)tuple元组:
创建元组:tup1 = ('physics', 'chemistry', 1997, 2000);
tup2 = (1, 2, 3, 4, 5 );
tup3 = ();
创建的元组只有一个元素时,需要在元素的后面添加逗号来消除歧义:
tup4 = (50,);
访问元组: 元组可以使用下标索引来访问元组中的值
修改元组:元组中的元素值是不允许修改的,但我们可以对元组进行连接组合
eg:tup1=(12,34,56)
tup2=('abc','xyz')
tup3=tup1+tup2
print(tup3)
输出为:(12,34,56,'abc','xyz')
删除元组:元组中的元素值是不允许删除的,但我们可以使用del语句来删除整个元组
del tup;
元组运算符:与字符串一样,元组之间可以使用 + 号和 * 号进行运算。这就意味着他们可以组合和复制,运算后会生成一个新的元组。
元组的索引截取:因为元组也是序列,所以也可以进行截取。和list的操作是一样的
元组的内置函数:cmp(tuple1,tuple2):比较两个元组的元素
len(tuple1,tuple2):元组的元素个数
max(tuple1,tuple2):返回元组中元素最大值
min(tuple):返回元组中元素最小值
tuple(seq):将列表转换为元组
tuple和list非常类似,但是tuple一旦初始化就不能修改
3)dict字典:
所以字典具有下列特性:
1、元素的查询和插入操作很快,基本上是常数级别
2、占用内存较大,采用的是空间换时间的方法
创建字典的方法:事先拼出整个字典:D1={'name':'bob','age':40}
如果需要动态地建立字典的一个字段:D2={}
D2['name']='bob'
D2['age']=40
D2
字典中键值遍历方法:D={'x':1,'y':2,'z':3}
for key,value in D.items():
print(key,'=>',value)
字典的赋值:e=d 引用赋值,e,d始终的一样的
e=d.copy()值赋值,二者之间没有关联的
删除元素:d.clear()删除d中的所有元素
d.pop('a')删除键值为'a'的元素
del d['a']删除键值为'a'的元素

字典的合并: dd = dict(dict1, **dict2)

802-878-2999

装饰器/迭代器/生成器/map/filter/lambda

1.装饰器:

1)在不修改原函数的代码的情况下,需要给该函数添加功能,这时候就可以用函数装饰器。
它会自动添加完功能并返回被装饰的这个函数。
python装饰器本质上就是一个函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外的功能,装饰器的返回值也是一个函数对象(函数的指)
实质: 是一个函数
参数:是你要装饰的函数名(并非函数调用)
返回:是装饰完的函数名(也非函数调用)
作用:为已经存在的对象添加额外的功能
特点:不需要对对象做任何的代码上的变动
eg:

import time
def decorator(func):
def wrapper(*args, **kwargs):
    start_time = time.time()
    func()
    end_time = time.time()
    print(end_time - start_time)
return wrapper

@decorator 
def func():
    time.sleep(0.8)
func() 

前面提到的都是让函数作为装饰器去装饰其他的函数或者方法,那么这里也可以让一个类发挥装饰器的作用。
2)类装饰器:

class Decorator(object):
    def __init__(self, f):
        self.f = f
    def __call__(self):
        print("decorator start")
        self.f()
         print("decorator end")

@Decorator
def func():
    print("func")
func()

注意:call()是一个特殊方法,它可将一个类实例变成一个可调用对象:
3)装饰器链:
一个python函数也可以被多个装饰器修饰,要是有多个装饰器,可见,多个装饰器的执行顺序:是从近到远依次执行。
4)python的装饰器库:functools
避免被装饰函数自身的信息丢失。

from functools import wraps
....
@wraps(func)
......

2.迭代器:

1)概述:迭代器是访问集合元素的一种方式。迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
2)延迟计算或惰性求值:
迭代器不要求你事先准备好整个迭代过程中所有的元素。仅仅是在迭代至某个元素时才计算该元素,而在这之前或之后,元素可以不存在或者被销毁。这个特点使得它特别适合用于遍历一些巨大的或是无限的集合。
3)可迭代对象:
迭代器提供了一个统一的访问集合的接口。只要是实现了iter()或getitem()方法的对象,就可以使用迭代器进行访问。
序列:字符串、列表、元组
非序列:字典、文件
自定义类:用户自定义的类实现了iter()或getitem()方法的对象
4)使用内建的工厂函数iter(iterable)可以获取迭代器对象:
eg:

l1=(1,2,3,4,5)
l2=iter(l1)
print(l2)
print(next(l2))
print(next(l2))
执行:
<tuple_iterator object at       0x00000147B3C67550>
1
2

for循环可用于任何可迭代对象
for循环开始时,会通过迭代协议传输给iter()内置函数,从而能够从迭代对象中获得一个迭代器,返回的对象含有需要的next()方法。

3.生成器:

1)Python的生成器是一个返回可以迭代对象的函数。
在一个一般函数中使用yield关键字,可以实现一个最简单的生成器,此时这个函数变成一个生成器函数。yield与return返回相同的值,区别在于return返回后,函数状态终止,而yield会保存当前函数的执行状态,在返回后,函数又回到之前保存的状态继续执行。局部变量和它们的状态会被保存,直到下一次调用。
2)生成器函数与一般函数的不同:
生成器函数包含一个或者多个yield
当调用生成器函数时,函数将返回一个对象,但是不会立刻向下执行
像iter()和next()方法等是自动实现的,所以我们可以通过next()方法对对象进行迭代
一旦函数被yield,函数会暂停,控制权返回调用者
局部变量和它们的状态会被保存,直到下一次调用

简单的生成器函数

def my_gen():
 n=1
 print("first")
 # yield区域
 yield n

 n+=1
 print("second")
 yield n

 n+=1
 print("third")
 yield n

a=my_gen()
print("next method:")
每次调用a的时候,函数都从之前保存的状态执行
print(next(a))
 print(next(a))
 print(next(a))
 print("for loop:")
与调用next等价的
b=my_gen()
for elem in my_gen():
 print(elem)

4.map/filter:

1)map()函数会根据提供的函数对指定的序列做映射。
map()函数语法:
map(function, iterable, ...)
function:一个或两个参数。
iterable:一个或多个序列
python2中返回list表
python3中返回一个对象
2)filter()函数是 Python 内置的另一个有用的高阶函数,filter()函数接收一个函数 f 和一个list,这个函数 f 的作用是对每个元素进行判断,返回 True或 False,filter()根据判断结果自动过滤掉不符合条件的元素,返回由符合条件元素组成的新list。
filter()函数语法:filter(function, iterable)
function:判断对象
iterable:可迭代对象
返回值:列表对象,和map()函数一样需要用list()转换成序列
3)python中的reduce内建函数是一个二元操作函数,他用来将一个数据集合(链表,元组等)中的所有数据进行下列操作:用传给reduce中的函数 func()(必须是一个二元操作函数)先对集合中的第1,2个数据进行操作,得到的结果再与第三个数据用func()函数运算,最后得到一个结果。
在 Python3 中,reduce() 函数已经被从全局名字空间里移除了,它现在被放置在 fucntools 模块里,如果想要使用它,则需要通过引入 functools 模块来调用 reduce() 函数:
eg:

from functools import reduce
def myadd(x,y):
    return (x+y)
sum=reduce(myadd,(1,2,3,4,5,6,7))
print(sum) 

5.lambda匿名函数:
1)编程中提到的 lambda 表达式,通常是在需要一个函数,但是又不想费神去命名一个函数的场合下使用,也就是指匿名函数。
lambda 函数是一个可以接收任意多个参数(包括可选参数)并且返回单个表达式值的匿名函数。
2)使用lambda函数的好处是:
lambda函数比较轻便,即用即扔,很适合需要完成某一项简单功能,但是这个简单的功能只在此一处使用,连名字都很随意的情况下;
lambda是匿名函数,一般用来给filter,map,reduce这样的函数式编程服务;

9085573469

模块测试(或单元测试)是对程序中的单个子程序,子程序或过程进行测试的过程,也就是说,一开始并不是对真个程序进行测试,而是首先将注意力集中在对构成程序的较小模块的测试上面。这样做的动机有三个:
(1)由于模块测试的注意力一开始集中在程序的较小单元上,因此它是一种管理组合的测试元素的手段。
(2)模块测试减轻了调式的难度,这是因为一旦某个错误被发现出来,我们就知道它在哪个具体的模块中。
(3)模块测试通过为我们提供同时测试多个模块的可能,将并行工程引入软件测试中。
测试用例设计:
在为模块测试设计测试用例时,需要使用两种类型的信息,模块的规格说明和模块的源代码。规格说明一般都规定了模块的输入和输出参数以及模块的功能。
模块测试的测试用例的设计过程如下:使用一种或多种白盒测试方法分析模块的逻辑结构,然后使用黑盒测试方法对照模块的规格说明以补充测试用例。
增量测试:
软件测试是否应先独立地测试每个模块,然后再将这些模块组装成完整的程序?还是先将下一步要测试的模块组装到测试完成的模块集合中,然后再进行测试?第一种方法称为非增量测试或“崩溃”测试,而第二种方法称为增量测试或集成。
测试单独的模块需要一个特殊的驱动模块和一个或多个桩模块。
下面十几个显而易见的结论:
(1)非增量测试所需的工作量要多一些
(2)如果使用了增量测试,可以较早地发现模块中与不匹配接口,不正确假设相关的编程错误
(3)因此如果使用了增量测试,调式会进行得容易一些,我们假定存在着与模块间接口或假设相关的编程错误(根据经验而来的合理假设),那么,如果使用非增量测试,直到整个程序组装之后,这些错误才会浮现出来。
(4)增量测试会将测试进行得更彻底
(5)非增量测试所占用的机器时间显得少一些
(6)模块测试阶段开始时,如果使用的是非增量测试,就会有更多的机会进行并行操作(也就是说,所有模块可以同时测试)
从而我们得出结论,增量测试要更好一些
自顶向下测试与自底向上测试:
在上面的讨论中,即增量测试要鱿鱼非增量测试,这里将讨论两种增量测试策略:自顶向下的测试和自底向上的测试。
“自顶向下的测试”与“自顶向下的开发”是同义词,但是“自顶向下的设计”则完全不同并且是独立的概念。
按自顶向下模式设计的程序既可使用自顶向下的方式,也可使用自底向上的方式进行增量测试。
自底向上的测试是一种增量测试。
自顶向下的测试:
自顶向下的测试是从程序的顶部或初始模块开始。唯一的原则是:要成为合乎条件的下一个模块,至少一个该模块的从属模块(调用它的模块)事先经过了测试。
自底向上的测试与自顶向下的测试是相对立的,自顶向下测试的优点成为自底向上测试的缺点,而自顶向下测试的缺点又成为自底向上测试的优点。
自底向上的策略开始于程序中的终端模块(此类模块不再调用其他任何模块)。
测试完这些模块后,同样没有最佳的方法来挑选要进行增量测试的下一个模块。唯一正确的原则是,要成为合乎条件的下一个模块,该模块所有从属模块(它调用的模块)都已经事先经过了测试。
当测试用例造成模块输出的实际结果与预期结果不匹配的情况时,存在两个可能的解释:要么该模块存在错误,要么预期的结果不正确(测试用例不正确)。为了将这种混乱降低到最小程度,应在测试执行之前对测试用例集进行审核或检查。

队列与栈

栈与队列:
栈是一个非常常见的数据结构,它在计算机领域被广泛应用,比如操作系统会给每个线程创建一个栈用来存储函数调用时各个函数的参数,返回地址及临时变量等。
栈的特点是后进先出,即最后被压入栈的元素会最先弹出来;
通常栈是一个不考虑排序的数据结构,我们需要O(n)的时间才能找到栈中的自大值与最小值。如果想要在O(1)时间内找到,需要对栈做特殊设计。
一.用两个栈实现一个队列:
思路设计:设有两个栈s1与s2.首先插入一个元素a,先把它插入到s1中,再压入两个元素b,c到s1中,此时栈1中就有了三个元素:a,b,c.而这时候的s2还是空着的。
此时栈s1中c位于顶。这时候我们试着删除一个元素,按照队列先进先出的原则首先应该被删除的应该是a,元素a存储在栈S1中,但并不是在栈顶上,因此不能进行直接删除,现在是s2发挥作用的时候了。
将s1中的所有元素逐个弹出压入到S2中,此时再进行删除。就是先删除的a了,随后就是b与c。
测试用例的设计:
(1)往空的队列里添加,删除元素。
(2)往非空的队列里添加,删除元素
(3)连续删除元素直至队列为空
python实现代码:

class QueueWithTwoStacks(object):
def __init__(self):
    self._stack1 = []
    self._stack2 = []

def appendTail(self,x):
    self._stack1.append(x)
def deleteHead(self):
    if self._stack2:
        return self._stack2.pop()
    else:
        if self._stack1:
            while self._stack1:
                self._stack2.append(self._stack1.pop())
                return self._stack2.pop()
            else:
                return None

if name=='main':
queue=QueueWithTwoStacks()
queue.appendTail('9')
queue.appendTail('10')
queue.appendTail('5')
print(queue.deleteHead())
二.相关题目:两个队列实现一个栈
用两个队列实现一个栈,首先设有两个队列分别为queue1,与queue2.
(1)往queue1里面插入三个数a,b,c
(2)把queue1里面的三个数依次弹出a与b到queue2,然后就可以删除c了,以此实现了栈的后进先出
(3)以此循环,删除剩余的数
python实现:

class StackWithTwoQueues(object):
def __init__(self):
    self._stack1 = []
    self._stack2 = []

def push(self,x):
    if len(self._stack1) == 0:
        self._stack1.append(x)
    elif len(self._stack2) == 0:
        self._stack2.append(x)
    if len(self._stack2) == 1 and len(self._stack1) >= 1:
        while self._stack1:
            self._stack2.append(self._stack1.pop(0))
    elif len(self._stack1) == 1 and len(self._stack2) > 1:
        while self._stack2:
            self._stack1.append(self._stack2.pop(0))

def pop(self):
    if self._stack1:
        return self._stack1.pop(0)
    elif self._stack2:
        return self._stack2.pop(0)
    else:
        return None

(419) 427-6764

一.检查与走查

代码检查与走查是两种主要的人工测试方法,这两种方法在这里有很多相似的共同之处。
代码检查与走查都要求人们组成一个小组来阅读或直观检查特定的程序。无论死哪一种方法,参加者都需要完成一些准备工作。准备工作的高潮是在参加者会议上的“头脑风暴会”。目的是找出错误来。但不必找出改正错误的方法。换句话说,是测试,而不是调试。
代码走查的另一个优点在于,一旦发现错误,通常就能在代码中对其进行精确的定位,这就降低了调式的成本。程序中的错误始终是未知的。
对于某些特定类型的错误,人工方法比基于计算机的方法更有效,而对于其他类型的错误,基于计算机的方法更有效。代码检查/走查与基于计算机的测试是互补的,缺少其中任何一种,错误检查的效率都会降低。
根据我们的经验,修改一个现存的程序比编写一个新程序更容易产生错误(以每写一行代码的错误数量计)
代码检查:
一个代码检查小组通常由四人组成,其中一人发挥着协调作用,协调人应该是个称职的程序员,但不是该程序的编码人员。小组中的第二个成员是该程序的编码人员,其他成员通常是程序的设计人员以及一名测试专家。
在代码检查之前的几天,协调人将程序清单和设计规范分发给其他成员。所有人员在检查之前熟悉这些材料。在检查进行时,主要进行两项活动:
1.由程序编码人员逐条语句讲述程序的逻辑结构。
2.对着历来常见的编码错误列表分析程序
代码走查:
代码走查与代码检查类似。
小结:大多数的软件项目都应该使用到以下的人工测试方法:
利用错误列表进行代码检查。
小组代码走查
桌面检查
同行评审

二.测试用例的设计

黑盒测试:等价类划分/边界值分析/因果图分析/错误测试
白盒测试:语句覆盖/判定覆盖/条件覆盖/(判定/条件覆盖)/多重条件覆盖
推荐步骤是:先试用黑盒测试来设计测试用例,然后视情况需要使用白盒测试方法来设计补充的测试用例。
1.白盒测试关注的是测试用例执行的程度或覆盖程序逻辑结构的程度。
2.判定覆盖或分支覆盖是较强一些的逻辑覆盖准则。
3.判定/条件覆盖准则的一个缺点是尽管看上去所有条件的所有结果似乎都执行到了,但由于有些特定的条件会屏蔽掉其他的条件,常常并不能全部都执行到。

等价划分:
使用等价划分方法设计测试用力主要有连个步骤:
(1)确定等价类
(2)生成测试用例
确定等价类:有效等价类代表对程序的有效输入,而无效等价类代表的则是其他任何可能的输入条件(即不正确的输入值)
生成测试用例:
使用等价类来生成测试用例,其过程如下:
a.为每个等价类设置一个不同的编号。
b.编写新的测试用例,尽可能多的覆盖那些尚未被涵盖的有效等价类,知道所有的有效等价类都被测试用例所覆盖(包含进去)。
c.编写新的用例,覆盖一个且仅一个尚未被覆盖的无效等价类,直到所有的无效等价类都被测试用例所覆盖。

边界值分析:
所谓边界条件,是指输入和输出等价类中那些恰好处于边界,或超过边界,或在边界以下的状态。
因果图:
边界值分析和等价划分的一个弱点是未对输入条件的组合进行分析。
因果图有助于用一个系统的方法选择出高效的测试用例集。它还有一个额外的好处,就是指出规格说明的不完整性和不明确之处。
因果图是一种形式的语言,用自然语言描述的规格说明可以转换为因果图。因果图实际上是一种数字逻辑电路。
约束符号:
E约束:表示最多只有一个为1;
I约束:表示至少有一个为1
O约束:有且仅有一个为1;
R约束:如果a为1,则b也必须为1.
M约束是在结果之间建立的约束条件:如果结果a为0则b强制为0
错误猜测:
其基本思想是列举出可能犯的错误或错误易发情况的清单,然后依据清单编写测试用例。
总结:测试策略
1.如果规格说明中包含输入条件组合的情况,应首先使用因果图分析方法
2.在任何情况下都应使用边界值分析方法,应记住,这是对输入和输出边界进行的分析。边界值分析可以产生一系列补充的测试条件。
3.应为输入和输出确定有效和无效等价类,在必要的情况下对上面确认的测试用例进行补充。
4.使用错误猜测技术增加更多的测试用例。
5.针对上述测试用例集检查程序的逻辑结构。应使用判定覆盖,条件覆盖,判定/条件覆盖或多重条件覆盖准则。

软件测试的艺术(1)

软件测试的心理学与经济学

1.对于测试,合适的定义是:"测试是为发现错误而执行程序的过程"
如果我们的目的是证明程序中存在错误,我们设计的测试数据就有可能更多地发现问题。
2.软件测试是一个破坏性的过程,甚至是一个"施虐"的过程,这就说明为什么大多数人都觉得它困难。
3.一个测试用例能够发现新的错误则说明它是值得设计的。一个"不成功"的测试用例,会使程序输出正确的结果,但不能发现任何错误。
4.在测试过程中,程序即使能够完成预定的功能,也仍然可能隐藏错误。也就是说。当程序没有实现预期功能时,错误是很明显的;如果程序做了不应该做的,这同样也是一个错误。
总结一下就是软件测试更合适被视为试图发现错误的破坏性的过程,而不是将其视为证明"软件做了其应该做的过程"。
当然,最终我们还是要通过软件测试来建立某种程度的信心。
5.一般来说,要发现程序中的所有错误也是不切实际的,常常也是不可能的。这个基本问题反过来暗示出软件测试经济学问题。
6.为了应对测试经济学的挑战,应该在开始测试之前建立某些策略。黑盒测试和白盒测试就是两种最普遍的策略。
黑盒测试:
黑盒测试是一种重要的测试策略。又称为数据驱动的测试或输入/输出驱动的测试。
使用这种测试方法时,将程序视为一个黑盒子。测试目标与程序的内部机制和结构完全无关,而是将重点集中放在发现程序不按其规范正确运行的环境条件。
穷举输入测试是无法实现的,这有两方面的意义,一是我们无法测试一个程序以确保它是无错的,二是软件检测中需要考虑的一个基本问题是软件测试的经济学。也就是说,由于穷举测试是不可能的,测试投入的目标在于通过有限的测试用例,最大限度的提高发现问题的数量。
白盒测试:另种测试策略称为白盒测试或逻辑驱动测试,允许我们检查程序的内部结构。
与黑盒测试中穷举输入测试相似的测试方法是穷举路径测试:即将程序中的每条语句至少执行一次。但是我们不难证明,这还是远远不够的。虽然,我们可以测试到程序中的所有路径,但是程序可能仍然存在一个错误,这有三个原因:
第一,即使是穷举路经测试也决不能保证程序符合设计规范。
第二,程序可能会因为缺失某些路径而存在问题。
第三,穷举路径测试可能不会暴露数据敏感错误。

软件测试的原则:

测试用例中一个必需部分是对预期输出或结果的定义
程序员应当避免测试自己编写的程序
编写软件的组织不应当测试自己编写的软件
应当彻底检查每个测试的执行结果
测试用例的编写不仅应当跟据有效和预期的输入情况,而且应当根据无效和未预料的输入情况。
检查程序是否“未做其应该做的”仅是测试的一半,测试的另一半是检查程序是否“做了不应该做的”
应避免测试用例用后即弃,除非软件本身就是一个一次性的软件。
计划测试工作时不应默许假定不会发现错误
程序某部分存在更多错误的可能性,与该部分已经发现错误的数目成正比。错误总是倾向于聚集存在。
软件测试是一项极富创造性,极具挑战性的工作。
需要牢记的三个重要的测试原则:
软件测试是为了发现错误而执行程序的过程
一个好的测试用例具有较高的发现某个尚未发现的错误的可能性
一个成功的测试用例能够发现某个尚未发现的错误

316-729-5733

一.树

树是在实际编程中经常遇到的数据结构,它的逻辑:除根节点之外,每个节点只有一个父节点,根节点没有父节点;除叶节点没有子节点之外其他节点都有一个或多个子节点。父节点和子节点之间用指针链接。
而平时提到的树大多数是指二叉树:
二叉树是树的一种结构,在二叉树中每个节点最多只能有两个节点,最重要的操作则是遍历。
通常树有如下三种遍历方式:
(1)前序遍历:根节点,左节点,右节点
(2)中序遍历:左节点,根节点,右节点
(3)后序遍历:左节点,右节点,根节点
宽度优先遍历:先访问树的第一层节点,再访问树的第二层节点。。。一直访问到最下面一层节点。在同一层节点中,以从左到右的顺序依次访问。
二叉树有很多的特例:二叉搜索树就是其中之一。在二叉搜索树中,左子节点总是小于或等于根节点,而右子节点总是大于或等于根节点。

二叉树的另外两个特例就是堆和红黑树。
堆分为最大堆和最小堆。在最大堆中根节点的值最大,在最小堆中根节点的值最小。
红黑树就是把树中的节点定义为红,黑两种颜色,并通过规则确保从根节点到叶节点的最长路径的长度不超过最短路径的两倍。

二.重建二叉树

输入二叉树的前序遍历和中序遍历的结果,重建该二叉树。
(1)首先可以根据前序和中序确定二叉树的根节点和左子树和右子树
(2)左3子树和右子树可以根据(1)中的方法分别再确定。递归的方法。

三.python实现二叉树(根据前序和中序实现二叉树)

class Node:
def __init__(self,data,left,right):
    self.data=data
    self.left=left
    self.right=right
def construct_tree(pre_order,mid_order):
if len(pre_order)==0:
    return None
root_data=pre_order[0]
for i in range(0,len(mid_order)):
    if mid_order[i]==root_data:
        break
left=construct_tree(pre_order[1:1+i],mid_order[:i])
right=construct_tree(pre_order[1+i:],mid_order[i+1:])
return Node(root_data, left, right)

if __name__ == '__main__':
pre_order=[1, 2, 4, 7, 3, 5, 6, 8]
mid_order=[4, 7, 2, 1, 5, 3, 8, 6]
root=construct_tree(pre_order,mid_order)
print(root.data)
print(root.left.data)
print(root.right.data)
print(root.left.left.data)
print(root.left.left.right.data)
print(root.right.right.data)
print(root.right.left.data)

四.测试用例设计:

1.普通二叉树(完全二叉树,不完全二叉树)
2.特殊二叉树(所有节点都没有右子节点的二叉树,所有节点都没有左子节点的二叉树。只有一个节点的二叉树)
3.特殊输入测试(二叉树的根节点指针为nullprt,输入的前序遍历序列和中序遍历序列不匹配)

(905) 746-1685

链表

1.链表的结构很简单,由指针把若干个节点链接成链状结构,链表是一种动态的数据结构,是因为在创建链表的时候不需要知道链表的长度,当插入一个节点时,我们只需要为新节点分配内存,然后来调节指针的指向来确保新节点被连入链表中。内存分配不是在创建链表时一次性完成的,而是每添加一个节点分配一次内存,由于没有闲置的内存,链表的空间效率比数组高。
2.由于链表中的内存不是一次性分配的,因而我们无法保证链表的内存是连续的。因此,如果想在链表中找到它的第I个节点,那么我们只能从头节点开始,沿着指向下一节点的指针遍历链表,它的时间效率为O(N).而在数组中,我们可以根据数组的下标在O(1)时间内直接找到第i个元素。

从尾到头打印链表:

链表的遍历是从头到尾的,而这里需要从尾到头打印出链表,也就是说第一个遍历出的节点需要最后输出,而最后一个遍历出来的需要第一个打印出来。因此这里就是典型的"先进后出'',由此想到了栈。

关于打印链表的测试用例:

(1).功能测试(输入的链表有多个节点,输入的链表只有一个节点)
(2).特殊测试用例(输入的链表头节点指针为nullptr)

如何用python语言来实现:

(1)先进后出,考虑到一个堆栈的使用:

class ListNode:
 def __init__(self, x):
   self.val = x
   self.next = None
from collections import deque
class Solution:
    def printListFromTailToHead(self,listNode):
        list_val=deque()
        while listNode:
            list_val.appendleft(listNode.val)
            listNode=listNode.next
        return list_val

(2)也可以运用list的性质直接倒转

class ListNode:
def __init__(self, x):
   self.val = x
   self.next = None
class Solution:
    def printListFromTailToHead(self,listNode):
        list_val=[]
        while listNode:
            list_val.append(liastNode.val)
            listNode=listNode.next
        return list_val[::-1] 

(3)可以直接使用python的内置函数来反转一个序列:

  a=[2,4,5,7,4,1,8]
  b=list(reversed(a))
  print(b)

(4)可以使用分片:

  a=[2,4,5,7,4,1,8]
  b=a[::-1]
  print(b)