This commit is contained in:
louzefeng
2024-07-09 18:38:56 +00:00
parent 8bafaef34d
commit bf99793fd0
6071 changed files with 1017944 additions and 0 deletions

View File

@@ -0,0 +1,107 @@
<audio id="audio" title="43 | 发挥人的潜能:探索式测试" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/bf/98/bf57797678f2d71289d1a8eab16a3d98.mp3"></audio>
你好,我是茹炳晟。今天我和你分享的主题是:发挥人的潜能之探索式测试。
从今天开始我们又要一起进入一个新的系列了测试新技术系列。在这个系列里我将通过5篇文章和你分享软件测试领域中比较新的5个测试主题探索性测试、测试驱动开发TDD、精准测试、渗透测试以及基于模型的测试。这五种新的测试技术是我精挑细选的初衷就是希望帮你拓宽知识面、思路。
今天这次的分享,我们就先从当下很热门的探索式测试开始吧。此时,你可能已经听说过了探索式测试,也很可能还不知道什么是探索式测试。这一切都没有关系,相信你经过我今天的这次分析,总能汲取到新的知识,对探索式测试有一个全面、清晰的认识。
## 软件测试与招聘面试类比
在正式开始介绍探索式测试之前,我们先一起看一个工作中招聘面试的例子吧。
假设,你是面试官,现在有一个候选人要应聘你负责的这个职位。那么,你通常都会在正式面试前,先仔细了解候选人的简历,然后根据简历情况以及这个职位的要求,设计一些高质量的面试问题。当然了,你这么做的目的是,试图通过这些面试问题判断候选人与这个职位的匹配程度。
但是,在实际面试的时候,你提出面试问题之后,通常会根据面试者的回答调整接下来的问题:
- 如果候选人的回答符合你的预期,你可能就会结束这个话题,然后开始一个新的话题去考察候选人其他方面的能力;
- 如果你对候选人的回答有疑问,你很可能就会顺着候选人的回答进行有针对性的提问,这时你提出的新问题完全可能是即兴发挥的,而不是事先准备好的。
事实上,你在面试候选人的过程中,其实同时在开展候选人评估、面试问题设计、面试执行和面试问题回答评估。可以说,这个面试过程其实就是你在“探索”你的候选人,通过“探索”去判断候选人是否符合你的要求。
那么对于软件测试来说,也有非常类似的过程。
比如,你首先根据软件功能描述来设计最初的测试用例,然后执行测试;测试执行后,可能你得到的输出和预期输出不完全一致,于是你会猜测这种不一致是否可能是软件的缺陷造成的;为了验证你的想法,你会根据错误输出设计新的测试用例,然后采用不同的输入再次检查软件的输出。
在一次测试中,你可能会经过几轮这样的猜测和验证,进行反复“探索”,最终确定了一个软件的缺陷。而这个过程中,你会发现,识别缺陷的思路和测试用例的设计,并没有出现在最初的测试设计和测试用例文档中,而是以很快的速度在你的脑海中以及实际测试执行和验证中快速迭代。
从本质上来看,上述的两个过程就是探索式测试最基本的思维模型了。所以说,探索式测试本身并不是一种测试技术,而是一种软件测试风格。这个测试风格,强调测试工程师要同时开展测试学习、测试设计、测试执行和测试结果评估等一系列的活动,以持续优化测试工作。
其实,在目前的工程实践中,探索式测试发现的缺陷最多,而且发现的缺陷也很有代表性。所以,现在很多企业在探索式测试中都有较大投入。
那么,我现在就和你分享一下什么是探索式测试吧。
## 什么是探索式测试?
探索式测试最早是由测试专家Cem Kaner博士在1983年提出的并受到当时语境驱动的软件测试学派的支持。后来Cem Kaner博士在佛罗里达工学院的同事James A. Whittaker凭借着在微软和谷歌担任测试架构师和测试总监的经验积累撰写了最早的探索式测试书籍Exploratory Software Testing扩展了探索式测试的概念和方法。
从本质上来看,探索式测试具有即兴发挥、快速实验、随时调整等特征, Kaner博士在2006年1月的Exploratory Testing Research Summit会议上将探索式测试的定义总结为以下的一句话在这里我直接引用了[原文](http://kaner.com/?p=46))。接下来,我就和你一起解读一下其中最关键的几个概念吧。
>
Exploratory software testing is a style of software testing that emphasizes the personal freedom and responsibility of the individual tester to continually optimize the value of her work by treating test-related learning, test design, test execution, and test result interpretation as mutually supportive activities that run in parallel throughout the project.
**首先,探索式测试是一种软件测试风格,而不是一种具体的软件测试技术**。作为一种思维方法,探索式测试强调依据当前语境与上下文选择最合适的测试技术。所以,切记不要将探索式测试误认为是一种测试技术,而应该理解为一种利用各种测试技术“探索”软件潜在缺陷的测试风格。
**其次,探索式测试强调独立测试工程师的个人自由和责任,其目的是为了持续优化其工作的价值**。测试工程师应该为软件产品负责,充分发挥主观能动性,在整体上持续优化个人和团队的产出。这种思想方法,与精益生产、敏捷软件开发的理念高度一致,这也正是探索式测试受到敏捷团队欢迎的原因之一。
这里需要特别指出的是,探索式测试对个人的能力有很高的依赖:同样的测试风格,由不同的人来具体执行,得到的结果可能会差别巨大。因此,对执行探索式测试的工程师的要求就会比较高,除了要能够从业务上深入理解被测系统外,还要有很强的逻辑分析与推理能力,当然对测试技术以及测试用例设计的融会贯通也是必不可少的技能。
**最后,探索式测试建议在整个项目过程中,将测试相关学习、测试设计、测试执行和测试结果解读作为相互支持的活动,并行执行**
注意这里的并行run in parallel并不是真正意义上的并行而是指对测试学习、测试设计、测试执行和测试分析的快速迭代即在较短的时间内比如1个小时或者30分钟内快速完成多次循环以此来不断收集反馈、调整测试、优化价值。这样在外部看来就会感觉这些活动是在并行地执行。
这样的理念与敏捷开发中的“小步快跑”、持续反馈的理念不谋而合,因此几乎所有的敏捷团队都会或多或少地应用探索式测试。而且,通常来讲,在敏捷开发的每个迭代中,新开发的功能基本都要依靠探索式测试保证产品的质量。
说到这里,你可能很容易陷入一个误区,那就是探索式测试看起来并没有书面严格意义上的测试设计文档,而且很多测试是在测试执行过程中即兴产生并执行的,那这样的测试风格是不是可以和即兴测试相提并论,两者又有什么区别和联系呢?
## 探索式测试与即兴测试的区别和联系
虽说探索式测试与即兴测试Ad-hoc Testing的风格看起来类似都是依靠测试工程师的经验和直觉来即兴发挥快速地试验被测试应用并不停地调整测试策略。但是探索式测试相比即兴测试更强调及时“反馈”的重要性。
在探索式测试中,测试工程师不断提出假设,通过测试执行去检验假设,通过解读测试结果证实或推翻假设。在这个迭代过程中,测试工程师不断完善头脑中被测试应用的知识体系,并建立被测应用的模型,然后利用模型、过往经验,以及测试技术驱动进一步的测试。
相比即兴测试完全不注重测试计划和设计,探索式测试要不停地优化测试模型和测试设计。由于测试设计和测试执行的切换速度很快,也就是说切换频率很高,所以会很容易传达“探索式测试没有测试计划和设计”这个错误的信息。然而,实际情况是,探索式测试有明确的测试目标和测试设计,只是测试设计的时间很短,会以很高的频率与测试执行交替切换。
掌握了探索式测试的基本理念之后,接下来我们再简单看一下探索式测试具体是如何实施的。
## 如何开展探索性测试?
探索式测试也是可以采用分层测试的策略。
通常,**我们首先会对软件的单一功能进行比较细致的探索式测试**。“探索”的过程主要是基于功能需求以及非功能性需求进行扩展和延伸期间可以采用类似“头脑风暴”的工具比如Xmind等帮助我们整理思路。
比如,软件系统的用户登录功能就是一个单一的功能,所以作为探索式测试人员,首先应该站在最终用户的角度去理解和使用登录功能。也就是说,探索式测试人员需要了解真正的业务需求,然后基于这些业务需求“探索”软件的功能是否可以满足业务需求。
为此,探索式测试人员需要分析出用户登录功能的所有原子输入项。这里为了简化,假定原子输入项只有用户名、密码和登录按钮。接着,组合这些原子输入项构成最基本典型的测试场景。至于什么才是最基本典型的场景,则取决于探索式测试人员对需求的理解程度。
比如,用真实合法的用户名以及密码完成登录就是一个非常基本典型的场景,如果该场景能够成功登录,就可以切换到下一个;如果该场景不能够成功登录,就需要去“探索”为什么没能登录成功,比如你可能会怀疑是否是因为用户名或者密码是区分大小写的,又或者是不是因为你多次错误的尝试而导致的。
基于你的怀疑,进一步去设计新的测试用例来验证你的猜测。如果你怀疑是用户名或者密码大小写不一致造成的登录失败,那么就需要尝试使用大小写完全一致的用户名和密码进行尝试,之后还应该故意设计一些测试用例采用相同字符但是不同大小写的用户名和密码去做尝试;如果你怀疑登录失败是由于过多次的失败登录引起的,你就应该故意去设计这样的场景来观察系统的实际行为是否符合你的猜想。
总之,通过以上这样的“探索”过程,你就将测试学习、测试设计、测试执行和测试结果评估串联成了一个快速迭代的过程,并在你脑海中快速建立了登录功能的详细模型。
**然后,我们往往会开展系统交互的探索式测试,这个过程通常会采用基于反馈的探索式测试方法**。基于反馈的探索式测试方法,会运用所有可用的测试技术,以及基于对产品深入理解后的技术直觉,并结合上一次测试结果的反馈与分析结果,指导测试工程师下一步的测试行动。
还是以用户登录功能为例,在系统交互的探索式测试中,你就不仅要考虑单一的登录功能了,而是要考虑用户登录与系统其他功能相结合的场景。
比如,你可以尝试不登录直接访问登录后的路径去观察系统的行为;再比如,你可以尝试不登录就去查看订单状态的操作等等。这些组合场景的设计主要取决于你想要验证,或者说想要“探索”的系统功能。很多时候这些灵感来自于你之前对系统的探索而取得的系统认识,同时你的技术直觉也在此扮演了重要角色。
最后,我要特别强调一下:其实,很多时候你已经在不知不觉中运用了探索式测试的思想方法,只是你自己并不知道这就是探索式测试而已。所以,探索式测试离你并不遥远,而且实现起来也没想象中的那么难。但是,探索式测试是否可以帮你找出尽可能多的缺陷,还是取决你对系统需求的理解以及根据过往经验而积累的技术直觉。而探索式测试,只是一个测试风格,或者说一个流程方法,所以其本身并不具备任何缺陷发现能力。
## 总结
通过今天这篇文章,我阐述了一个基本思想是:探索式测试是一种软件测试风格,而不是一种具体的软件测试技术。
作为一种思维方法,探索式测试强调依据当前语境与上下文选择最适合的测试技术,并且强调独立测试工程师的个人自由和责任,其目的是为了持续优化其工作的价值。
探索式测试建议在整个项目中,将测试相关学习、测试设计、测试执行和测试结果解读作为相互支持的活动,并行地执行。
从中我们可以看出,探索式测试的思想方法和精益生产、敏捷软件开发的理念高度一致,这也是探索式测试颇受敏捷团队欢迎的原因之一。
## 思考题
如果查阅探索式测试相关的技术专著,你会发现探索式测试有很多方法和工具,从中你有哪些收获呢?如果再结合你在实际项目中使用探索式测试的经验,你又有哪些心得体会呢?
欢迎你给我留言,我们一起讨论。<br />

View File

@@ -0,0 +1,284 @@
<audio id="audio" title="44 | 测试先行:测试驱动开发(TDD)" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d2/1c/d21837bd7c3827df75947c47a2746b1c.mp3"></audio>
你好我是茹炳晟。今天我和你分享的主题是“测试先行测试驱动开发TDD”。
通过上一篇文章我们已经深入理解了什么是探索式测试以及如何用探索式测试开展具体的测试。今天我这次分享的目的就是和你聊聊软件测试领域中的另一个很热门的话题测试驱动开发也就是Test-Driven Development通常简称为TDD。
听上去有些迷惑是不是测试怎么可能驱动开发呢在传统软件的开发流程中软件开发人员先开发好功能代码再针对这些功能设计测试用例、实现测试脚本以此保证开发的这些功能的正确性和稳定性。那么TDD从字面上理解就是要让测试先行这又是怎么一回事呢
确切地说TDD并不是一门技术而是一种开发理念。它的核心思想是在开发人员实现功能代码前先设计好测试用例的代码然后再根据测试用例的代码编写产品的功能代码最终目的是让开发前设计的测试用例代码都能够顺利执行通过。
这样对于开发人员来说,他就需要参与到这个功能的完整设计过程中,而不是凭自己想象去开发一个功能。他有一个非常明确的目标,就是要让提前设计的测试用例都可以顺利通过,为此,他先实现测试用例要求的功能,再通过不断修改和完善,让产品代码可以满足测试用例,可以说是“小而美”的开发过程。
所以从本质上来讲TDD并不属于测试技术的范畴。那么我为什么还要单独用一篇文章和你分享这个主题呢因为TDD中通常会用到很多常见的自动化测试技术使得测试在整个软件生命周期中的重要性和地位得到了大幅提升。
可以说TDD的思想和理念给软件研发流程带来了颠覆性的变化使得测试工作从原本软件研发生命周期的最后端走向了最前端。也就是说原本测试工作是软件研发生命周期最后的一个环节而现在TDD相当于把测试提到了需求定义的高度跑到了软件研发生命周期最前面。
那么接下来我们就一起看看TDD的优势有哪些以及TDD的具体实施过程。
## TDD的优势
TDD的优势可以概括为以下五个方面
1. **保证开发的功能一定是符合实际需求的。**
用户需求才应该是软件开发的源头,但在实际的软件开发过程中,往往会在不知情的情况下,或者自己的主观判断下,开发出一个完全没有实际应用场景的功能。而这些没有实际应用场景的功能,却因为产品验证和测试工作介入的时机都在项目后期,所以往往在集成测试中或者产品上线后才会被发现。
比如,开发人员在实现用户注册的功能时,认为需要提供使用手机号注册的功能。但是,这个功能开发完成后,测试人员却告知开发人员这个功能用不上,或者产品上线后才发现这个功能在实际场景中完全不是必须的,因为用户可以使用邮箱注册,然后再通过绑定手机号实现手机号登陆。所以,直接用手机号注册这个功能是不需要的,真正需要的是绑定邮箱和手机号的功能。
试想一下,如果是测试驱动开发,即先根据用户的实际需求编写测试用例,再根据测试用例来完成功能代码,就不会出现这种既浪费时间、精力,又没有必要的功能了。
1. **更加灵活的迭代方式。**
传统的需求文档往往会从比较高的层次去描述功能。开发人员面对这种抽象的需求文档往往会感觉无从下手。但是在TDD的流程里需求是以测试用例描述的非常具体。那么开发人员拿到这样的需求时就可以先开发一个很明确的、针对用户某一个小需求的功能代码。
在开发过程中,开发人员可以不断的调试这个功能,通过测试-&gt;失败-修改/重构-&gt;测试-&gt;成功的过程,使开发的代码符合预期,而不是等所有功能开发完成后,再将一个笨重的产品交给测试人员进行一个长周期的测试,发现缺陷后再整个打回来修改,然后由此又可能会引入新的缺陷。
另外,如果用户需求有变化,我们能够很快地定位到要修改的功能,从而实现快速修改。
1. **保证系统的可扩展性。**
为了满足测试先行的灵活迭代方式,我们会要求开发人员设计更松耦合的系统,以保证它的可扩展性和易修改性。这就要求,开发人员在设计系统时,要考虑它的整体架构,搭建系统的骨架,提供规范的接口定义而非具体的功能类。
这样,当用户需求有变化时,或者有新增测试用例时,能够通过设计的接口快速实现新功能,满足新的测试场景。
1. **更好的质量保证。**
TDD要求测试先于开发也就是说在每次新增功能时都需要先用测试用例去验证功能是否运行正常并运行所有的测试来保证整个系统的质量。在这个测试先行的过程中开发人员会不断调试功能模块、优化设计、重构代码使其能够满足所有测试场景。所以很多的代码实现缺陷和系统设计漏洞都会在这个不断调优的过程中暴露出来。
也就是说TDD可以保证更好的产品质量。
1. **测试用例即文档。**
因为在TDD过程中编写的测试用例首先一定是贴合用户实际需求的然后又在开发调试的过程中经过了千锤百炼即一定是符合系统的业务逻辑的所以我们直接将测试用例生成需求文档。
这里直接将测试用例生成需求文档的方法有很多、很简单的方法比如JavaDoc。
这样,我们就无须再花费额外的精力,去撰写需求文档了。
你看TDD真的是优势多多吧。那么接下来我们就一起来看看实施TDD的具体过程。
## 测试驱动开发的实施过程
站在全局的角度来看TDD的整个过程遵循以下流程
<li>
为需要实现的新功能添加一批测试;
</li>
<li>
运行所有测试,看看新添加的测试是否失败;
</li>
<li>
编写实现软件新功能的实现代码;
</li>
<li>
再次运行所有的测试,看是否有测试失败;
</li>
<li>
重构代码;
</li>
<li>
重复以上步骤直到所有测试通过。
</li>
接下来我们就通过一个具体的例子来看看TDD的整个流程吧。
我们现在要实现这么一个功能:用户输入自己的生日,就可以输出还要多少天到下次生日。
根据TDD测试先行的原则我们**首先要做的是设计测试用例。**
测试用例一用户输入空字符串或者null
```
@Test
//测试输入空字符串null时是否抛出&quot;Birthday should not be null or empty&quot;异常
public void birthdayIsNull() {
RuntimeException exception = null;
try {
BirthdayCaculator.caculate(null);
}catch(RuntimeException e) {
exception = e;
}
Assert.assertNotNull(exception);
Assert.assertEquals(exception.getMessage(), &quot;Birthday should not be null or empty&quot;);
}
@Test
//测试输入空字符串&quot;&quot;时,是否抛出&quot;Birthday should not be null or empty&quot;异常
public void birthdayIsEmpty() {
RuntimeException exception = null;
try {
BirthdayCaculator.caculate(&quot;&quot;);
}catch(RuntimeException e) {
exception = e;
}
Assert.assertNotNull(exception);
Assert.assertEquals(exception.getMessage(), &quot;Birthday should not be null or empty&quot;);
}
```
根据这个测试用例我们可以很容易地写出这部分的Java代码
```
public static int caculate(String birthday) {
if(birthday == null || birthday.isEmpty()) {
throw new RuntimeException(&quot;Birthday should not be null or empty&quot;);
}
}
```
测试用例二用户输入的生日格式不符合YYYY-MM-dd的格式
```
@Test
//测试输入错误的时间格式,是否抛出&quot;Birthday format is invalid!&quot;异常
public void birthdayFormatIsInvalid() {
RuntimeException exception = null;
try {
BirthdayCaculator.caculate(&quot;Sep 3, 1996&quot;);
}catch(RuntimeException e) {
exception = e;
}
Assert.assertNotNull(exception);
Assert.assertEquals(exception.getMessage(), &quot;Birthday format is invalid&quot;);
}
```
那么这部分的Java代码实现便要catch住ParseException, 重新自定义错误信息并抛出异常。
```
SimpleDateFormat sdf = new SimpleDateFormat(&quot;yyyy-MM-dd&quot;);
Calendar birthDate = Calendar.getInstance();
try {
//使用SimpleDateFormat来格式化输入日期值
birthDate.setTime(sdf.parse(birthday));
} catch (ParseException e) {
throw new RuntimeException(&quot;Birthday format is invalid!&quot;);
}
```
测试用例三,用户输入的生日格式正确,但是今年的生日已经过了,就应该返回离明年的生日还有多少天:
```
@Test
//测试用户输入的日期晚于今年生日的情况,判断是否返回离明年的生日有多少天
public void thisYearBirthdayPassed() {
Calendar birthday = Calendar.getInstance();
birthday.add(Calendar.DATE, -1);
SimpleDateFormat sdf = new SimpleDateFormat(&quot;YYYY-MM-dd&quot;);
String date = sdf.format(birthday.getTime());
int days = BirthdayCaculator.caculate(date);
//天数不应该出现负数
Assert.assertTrue(days &gt; 0);
}
```
测试用例四用户输入的生日格式正确且今年生日还没过返回的结果应该不大于365天
```
@Test
//测试用户输入的日期早于今年生日的情况判断返回的天数是否小于365
public void thisYearBirthdayNotPass() {
Calendar birthday = Calendar.getInstance();
birthday.add(Calendar.DATE, 5);
SimpleDateFormat sdf = new SimpleDateFormat(&quot;YYYY-MM-dd&quot;);
String date = sdf.format(birthday.getTime());
int days = BirthdayCaculator.caculate(date);
//天数不应该大于一年的天数365天
Assert.assertTrue(days &lt; 365);
}
```
测试用例五用户输入的生日格式正确并且是今天返回的结果应该为0
```
@Test
//测试用户输入的日期恰好等于今年生日的情况判断返回的天数是否是0
public void todayIsBirthday() {
Calendar birthday = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat(&quot;YYYY-MM-dd&quot;);
String date = sdf.format(birthday.getTime());
int days = BirthdayCaculator.caculate(date);
Assert.assertEquals(days, 0);
}
```
综合上述五种测试场景,根据测试用例,我们可以编写完整的功能代码覆盖所有类型的用户输入,完整代码如下:
```
public static int caculate(String birthday) {
//首先对输入的日期是否是null或者是&quot;&quot;进行判断
if(birthday == null || birthday.isEmpty()) {
throw new RuntimeException(&quot;Birthday should not be null or empty&quot;);
}
SimpleDateFormat sdf = new SimpleDateFormat(&quot;yyyy-MM-dd&quot;);
Calendar today = Calendar.getInstance();
//处理输入的日期恰好等于今年生日的情况
if(birthday.equals(sdf.format(today.getTime()))) {
return 0;
}
//输入日期格式的有效性检查
Calendar birthDate = Calendar.getInstance();
try {
birthDate.setTime(sdf.parse(birthday));
} catch (ParseException e) {
throw new RuntimeException(&quot;Birthday format is invalid!&quot;);
}
birthDate.set(Calendar.YEAR, today.get(Calendar.YEAR));
//实际计算的逻辑
int days;
if (birthDate.get(Calendar.DAY_OF_YEAR) &lt; today.get(Calendar.DAY_OF_YEAR)) {
days = today.getActualMaximum(Calendar.DAY_OF_YEAR) - today.get(Calendar.DAY_OF_YEAR);
days += birthDate.get(Calendar.DAY_OF_YEAR);
} else {
days = birthDate.get(Calendar.DAY_OF_YEAR) - today.get(Calendar.DAY_OF_YEAR);
}
return days;
}
```
以上场景每添加一个新的功能点都会添加一个测试方法完成新功能点的软件代码后接着运行当前所有的测试用例以保证新加的功能代码能够满足现有的测试需求。这就是一个典型的TDD过程了。但是在实际开发场景肯定会更复杂 你想要用TDD思想写出健壮稳定的代码就需要深入理解TDD中的每一步。
**首先需要控制TDD测试用例的粒度**。如果测试用例并不是最小粒度的单元测试,开发人员就不能不假思索地直接根据测试用例开发功能代码,而应该先把测试用例分解成更小粒度的任务列表,保证每一个任务列表都是一个最小的功能模块。
在开发过程中,要把测试用例当成用户,不断分析他可能会怎样调用这个功能,大到功能的设计是用类还是接口,小到方法的参数类型,都要充分考虑到用户的使用场景。
**其次,要注意代码的简洁和高效**。随着功能代码的增加开发人员为了让测试能顺利通过很可能会简单粗暴地使用复制粘贴来完成某个功能而这就违背了TDD的初衷本来是为了写出更优雅的代码结果反而造成了代码冗余混乱。因此在开发-测试循环过程中,我们要不断地检查代码,时刻注意是否有重复代码、以及不需要的功能,将功能代码变得更加高效优雅。
**最后,通过重构保证最终交付代码的优雅和简洁**。所有功能代码都完成所有测试都通过之后我们就要考虑重构了。这里可以考虑类名、方法名甚至变量名命名是否规范且有意义太长的类可以考虑拆分从系统角度检查是否有重复代码是否有可以合并的代码你也可以参考市面上比较权威的关于重构的书完成整个系统的重构和优化。这里我建议你阅读Martin Fowler的《重构改善既有代码的设计》这本书。
**总的来说TDD有其优于传统开发的特点但在实际开发过程中我们应该具体场景具体分析。**
比如最典型的一个场景就是一个旧系统需要翻新重做并且针对这个老系统已经有很多不错的测试用例了这就很适合选用TDD。
总之我们可以通过分析当前的时间、人、方式、效果各要素来最终决定是否选用TDD。另外需要特别注意的是选用TDD并不是测试人员或者测试部门的事情而是需要公司层面的流程和体系的配合也正是这种原因虽然大家都能看到TDD的优势但是在实际项目中的运用还是比较有限。
## 总结
今天我和你分享了测试驱动开发的核心理念以及TDD的优势。
TDD的核心思想便是在开发人员实现功能代码前先设计好测试用例编写测试代码然后再针对新增的测试代码来编写产品的功能代码最终目的是让新增的测试代码能够通过。
相对于传统软件开发流程TDD的优势主要包括对需求精准的把控、更灵活的迭代、促使更好的系统设计、更好的交付质量以及轻量级的文档等。
最后我用用“用户输入自己的生日就可以输出还要多少天到下次生日”作为例子展示了测试驱动开发的完整流程希望帮助你对TDD有更直观的认识。
## 思考题
在实际的工程项目中你实际使用过TDD吗如果有的话是否可以分享一下你的实践心得如果没有的话你是否可以设象一下你会怎么规划和设计一个TDD的项目
感谢你的收听,欢迎你给我留言一起讨论。

View File

@@ -0,0 +1,156 @@
<audio id="audio" title="45 | 打蛇打七寸:精准测试" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/83/8c/83b02145471a9d3cb9c67062e0e9998c.mp3"></audio>
你好,我是茹炳晟。今天我和你分享的主题是“打蛇打七寸:精准测试”。
在前面的两篇文章中,我和你分享了探索式测试和测试驱动开发的概念、具体的实施方法。今天,我会继续和你分享软件测试领域中的另一个前沿话题:精准测试。
软件测试行业从最开始的手工测试到自动化测试,从黑盒测试到白盒测试,测试理念和技术都发生了日新月异的变化。现如今,几乎所有的软件公司都有一套强大且复杂的自动化测试用例,用来夜以继日地保证产品的正确性和稳定性。
然而,你有没有想过,现在你所掌握的软件测试技术和用例,真的是最准确、最适合你的产品的吗?这其中,是不是存在很多冗余的测试数据、根本用不上的测试用例、永远成功不了的测试场景?
更糟糕的是,当产品代码有更新时,你根本不知道这些更新到底影响了哪些功能,也无法精准地选取测试用例,而不得不执行完整的全回归测试。
针对这类问题精准测试的概念在2016年被提了出来。**所谓精准测试,就是借助一定的技术手段、通过算法的辅助对传统软件测试过程进行可视化、分析以及优化的过程**。也就是说,精准测试可以使得测试过程可视、智能、可信和精准。
为了可以帮助你更好地理解,为什么要有精准测试,以及它可以解决什么问题,我在和你分享精准测试的内容时,会先和你一起分析传统软件测试正面临着哪些痛点,而精准测试又是如何解决这些痛点的。
## 传统软件测试的主要短板
现如今,软件产品的规模以及复杂度的发展速度,可谓超乎想象,而传统的软件测试方法,在面临这些挑战时已经表现出了些许力不从心。这些力不从心,也就是传统软件测试的短板,我归纳为了下面这五大类:
**第一大短板,测试的维护成本日益升高。**
当传统测试的用例逐渐增加时,需要花费越来越大的时间和人力成本,去维护一个庞大的测试用例集,以此保证产品新特性和老功能的正确性和稳定性。
而在这成千上万的测试用例中,有很多陈旧的用例已经失效了(不再能满足现有产品的测试需求了),但是整个团队还是要花费很多精力去维护这个庞大的测试用例集。
**第二大短板,测试过程的低效。**
随着软件功能不断丰富,相应的测试用例集也愈加庞大,这时难免会出现“杀虫剂”效应,即:测试用例越来越多,而产品的“免疫力”也越来越强。
造成这种问题的原因是我们在测试早期已经发现了80%的软件缺陷,除非再花费巨大的人力和时间成本去分析和增加大批量的测试用例,否则后期新增的测试用例已经很难再发现新的缺陷了。
而精准测试可以通过对已有测试数据的跟踪分析来定位或者缩小测试范围以此减少发现剩下20%的软件缺陷的工作量。
**第三大短板,缺乏有效的回归用例选取机制。**
在传统测试理念中,每次添加新功能或者修复缺陷,一般都需要在产品上线前进行一轮全回归测试,哪怕这次的改动只有一行代码。但是,全回归测试的测试用例数量以及执行代价一般都比较大。
这里,我们之所以要采用全回归测试,是因为我们无法准确地知道这次的更新到底会影响哪些功能,也无法知道应该从回归测试中选取哪些必要的测试用例,无奈之下只能两眼一抹黑地执行全部用例。
**第四大短板,测试结果的可信度不高。**
在传统的软件测试中,测试数据的统计分析人工因素占据了绝大部分比重,由此导致测试数据本身的技术公信力不够高,进而需要依靠管理手段来保证真实的测试数据被准确地记录。
这种做法不仅可靠性差,而且执行成本高。
**第五大短板,无论是白盒测试技术还是黑盒测试技术都有其局限性。**
如果完全基于黑盒测试,那么注定无法深入代码实现的细节,也就无法做到有的放矢地设计测试;而如果基于白盒测试技术,为了保证代码质量,往往会采用代码级测试和代码覆盖率技术。
但是,这些测试方法都强依赖于产品代码,一旦代码发生改变,很多测试都会因此失效,因此很难适应高速迭代的开发流程。
另外,由于目前的代码级测试和代码覆盖率技术还不支持测试用例级别的覆盖率分析,而是要将所有测试结果混在一起,导致白盒测试时无法区分代码覆盖率的贡献到底来自于哪个测试用例,这将极大地限制白盒测试工具在测试结果分析上的应用。
## 精准测试的核心思想
而,精准测试便是为了解决传统测试的这些短板。它的核心思想是,借助一些高效的算法和工具,收集、可视化并且分析原生的测试数据,从而建立起一套测试分析系统。
所以,精准测试的主要特征可以概括为以下几个方面:
**第一,精准测试是对传统测试的补充**。精准测试是基于传统测试数据的,并不会改变传统的软件测试方法,更不会取代传统测试。也就是说,精准测试在不改变原有测试集的基础下,能够优化测试过程和数据,提高测试效率。
**第二,精准测试采用的是黑盒测试与白盒测试相结合的模式**。在执行黑盒测试时,收集程序自动产生的白盒级别的运行数据,然后通过可视化或者智能算法识别出测试未覆盖的点,继而引导开发人员和测试人员有的放矢地补充测试用例。
同时,在黑盒测试的执行过程中,可以实现测试用例和产品代码的自动关联,将基于黑盒的功能测试直接映射到基于白盒的代码层,这将使智能回归测试用例选取的想法成为可能。
**第三,精准测试的数据可信度高**。精准测试的数据都是由系统自动录入和管理的,人工无法直接修改数据,因此我们可以直接将传统测试产生的数据导入精准测试系统,用于测试结果的分析,从而使测试结果具有更高的可信度。
**第四,精准测试过程中,不直接面对产品代码**。精准测试通过算法和软件实现对测试数据和过程的采集,因此并不会直接面向代码,也就不会强依赖于产品代码。
但是,精准测试能够实现测试用例和产品代码的自动关联,也就是说代码覆盖率的统计可以以测试用例为单位来进行,具体实现的核心思想还是基于代码覆盖率的统计,只是在代码覆盖率的元数据上增加了测试用例的信息。
因此,代码的改变并不会影响测试过程,但却能够将功能测试间接映射到代码级别。这样,精准测试就实现了测试用例和被测产品代码的双向追溯**。**
**第五,精准测试是与平台无关的、多维度的测试分析算法系统**。精准测试系统是一种通用的测试分析系统,独立于任何测试平台,其内部算法和业务无关,因此适用于各种不同的产品。
同时,精准测试为我们提供了多维度的测试分析算法,拓展了白盒测试的范畴。而,精准测试对测试用例和产品代码的自动关联,使得它可以为测试过程提供大量的智能分析结果。
接下来,我们再一起看看精准测试具体有哪些方法。
## 精准测试的具体方法
目前业界最成熟并且已经产品化的精准测试体系,来自于国内公司“星云测试”。所以,下面关于精准测试的具体方法的分享中,涉及到的很多概念我都参考了其官网的《星云精准测试白皮书》。如果你对完整的白皮书内容感兴趣的话,可以参考[http://www.threadingtest.com/index.html](http://www.threadingtest.com/index.html)。
目前,由星云测试实现的精准测试平台中,核心组件包括精准测试示波器、测试用例和被测产品代码的双向追溯、智能回归测试用例选取,以及测试用例聚类分析这四项最关键的技术。在这其中,最为核心的技术是测试用例和产品代码的双向追溯。
接下来我会依次和你分享这4项核心技术希望借此加强你对精准测试的理解。
### 软件精准测试示波器
软件精准测试示波器,即在软件测试(人工测试或者自动化测试)的过程中,自动分析代码运行的一些数据指标,并将其用图表的方式实时显示出来。其中,这些数据指标包括了代码的逻辑块执行速率、代码的条件执行速率、函数的调用速率等等。
同时,由于示波器记录了每个测试用例的产品代码执行序列,因此可以通过比较两个测试用例的产品代码执行序列来判断两个测试用例是否隶属于同一个等价类,这将有助于精简测试用例的数量。
另外,由于示波器所有的数据都是通过系统自动导入的,因此不存在人工导入可能引入的数据误差,借此保证了所有数据的分析和显示都是真实且可靠的。
最终,示波器以类似心电图的形状实时显示测试过程中被测代码的运行信息,因此我们可以很直观地看到测试中发生的变化。一旦测试过程稍有异常,就会立刻显示在示波器上,我们通过图形的变化就可以轻易地对平时不可见的程序行为进行分析,并作出判断。比如,是否存在计算密集的区域,是否有不该执行的代码在后台运行等。
### 测试用例和被测产品代码的双向追溯
顾名思义,测试用例和被测产品代码的双向追溯,就是通过一定的技术手段实现测试用例和被测产品代码的双向关联。这样,我们可以通过测试用例追溯到其执行的代码,也可以通过分析代码的功能为测试提供数据。
这里,**测试用例和被测代码的双向追溯,包括正向追溯和反向追溯。**
其中,正向追溯,即通过示波器将产品代码和测试用例进行自动关联。这个关联,可精确到方法或者代码块级别。而在关联之后,精准测试系统可以显示每个测试用例实际执行的代码。这样,当我们发现软件缺陷时,便可以快速定位出其所在的代码。
反向追溯是指,如果我要关注程序中的某一块代码,那么就可以通过精准测试系统追溯到所有测试这块代码的测试用例。这样,就使得测试数据便于统计和量化,同时测试和开发工程师之间就可以基于测试数据进行交流,为他们的沟通提供更有效的桥梁,降低沟通成本。
这里我画了一张图,来帮助你更好地理解正向追溯和反向追溯的概念。
<img src="https://static001.geekbang.org/resource/image/64/ef/644bcdeca4c82cf13d7a037c16ab6eef.png" alt="" />
总而言之,测试用例和被测产品代码的双向追溯能显著提升测试效率:
- 当我们发现了软件缺陷和错误时,通过这个方法可以迅速定位到有问题的代码逻辑;
- 当出现一些难以复现的缺陷时,这个方法可以帮助我们追溯有问题的代码而无需强行复现。
说到这里,你可能会有一个疑问,双向追溯技术后台一定是采用了代码覆盖率的统计工具,但是这个代码覆盖率统计工具和双向追溯又具体有什么区别和联系呢?
事实上,这两者之间最大的区别,体现在测试覆盖率的统计方式上。传统的代码覆盖率统计工具,会把所有测试产生的覆盖率混在一起,并不具备单个测试用例的覆盖率统计功能;而精准测试中的双向追溯技术,则可以将覆盖率的分析和计算精确到每个测试用例针对的产品代码。
另外,从实际工程的角度来看,传统的代码覆盖率统计工具都是单机运行,然后完成数据的统计,无法有效整合一个团队下多人的测试结果,也不能按照日期累计。而,现如今的双向追溯技术,则支持多人异地测试、整合计算覆盖率等功能。
### 智能回归测试用例选取算法
回归测试,就是在修复了某个错误或缺陷后,再对软件进行测试以确保没有引入新的错误或缺陷。而,智能回归测试用例选取算法便是针对需要执行的回归测试,通过算法得出各个测试用例的权重和优先级,使得在有限的时间和人力下,能够更高效地执行测试用例。
由于精准测试提供了智能算法来自动选取回归测试用例,因此既避免了人工选取回归测试用例时可能存在的测试盲点,也减少了执行回归测试的时间,同时还能够保证计算结果的精确性,大大降低了回归测试的风险。
另外,精准测试中测试用例和被测产品代码的双向追溯性,也使得当有代码变更需要执行回归测试时,可以直接找到具体应该执行哪些测试用例。
### 测试用例的聚类分析
测试用例的聚类分析是指通过建立测试用例和代码执行的剖面关系实现对测试用例的聚类分析。这个聚类分析的结果将以两维数据呈现出来测试用例ID及其对应的代码执行剖面。
通过聚类数据我们可以很容易地发现测试用例的执行错误。比如测试用例A应该执行代码块A而通过聚类分析我们发现用例执行完被分在了代码块B上。因此我们就可以断定该测试用例发生了错误或者测试环境出现了问题。
同时,测试用例的聚类分析能够展示测试用例的分布情况,为我们调整测试用例的分布提供依据。也就是说,我们可以通过这个数据,对测试用例聚集较少的区域予以补充丰富,同时也可以在测试用例聚集丰富的区域内提取出相对重要的用例,然后执行,从而节省时间、提高测试效率。
精准测试的概念和理论体系虽然比较完善,但就目前来看实际落地的案例还比较少。有些公司可能并不会直接去使用星云测试的平台,而是会基于精准测试的理念和方法去开发自己的工具。比如,有些国内互联网公司,就自己实现了基于增量的代码覆盖率统计方案,以及具有双向追溯功能的代码覆盖率方案。
## 总结
在今天这篇文章中,我和你分享了传统软件测试的方法、理念,因为测试用例数量持续增加而导致的用例维护成本高、测试过程低效、缺乏有效的回归测试用例选取机制等等一系列的问题,而有些力不从心。于是,精准测试应运而生了。
可以说,精准测试是通过一系列的智能算法和技术实现了对测试过程的管理。它可以在测试运行时,分析源数据指标以指导传统测试,并在一次次的修正中大幅提升测试效率。并且,精准测试在测试过程中产生的海量精准数据,即使不在测试周期里,也可以进行分析和追溯,让测试变得更加高效和有价值。
所以说,精准测试在节省了人力成本的同时,保证了软件的质量。
## 思考题
在这里,我并没有和你分享精准测试的所有概念,但是我建议你可以仔细阅读一下星云测试的《精准测试框架白皮书》。然后,你可以再思考一下,还有哪些项目和产品更适于开展精准测试。
如果你还有任何关于精准测试的疑问,欢迎给我留言一起讨论。

View File

@@ -0,0 +1,199 @@
<audio id="audio" title="46 | 安全第一:渗透测试" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/05/05/05482707dcbc549cd0a2d2ae6a62c105.mp3"></audio>
你好,我是茹炳晟。今天我和你分享的主题是:安全第一之渗透测试。
随着互联网的发展,网络环境越来越复杂,各类软件涉及的领域也越来越多,这时系统与软件的安全问题就愈加重要了。各类隐私信息、财务信息等的泄露,稍有不慎就会造成难以挽回的损失。
所以,大多数的公司,尤其是中大型的公司,已经针对系统与软件的安全采取了很多手段。比如,安装杀毒软件、定期给系统打补丁、定期进行漏洞及安全扫描、测试并封杀应用自身的安全漏洞等等。
虽说这些措施已经可以防止大部分的安全漏洞了,但却还不足以完全保证系统的安全性。这个时候,渗透测试便以其独立的“风姿”出现在了你我的视野里。
那么,接下来我们就一起看看什么是渗透测试,以及具体如何执行渗透测试吧。
## 渗透测试的定义
渗透测试指的是,由专业安全人员模拟黑客,从其可能存在的位置对系统进行攻击测试,在真正的黑客入侵前找到隐藏的安全漏洞,从而达到保护系统安全的目的。
或许你会有这样的疑问,软件系统在研发阶段已经用了各种手段保证安全性,为什么还需要进行渗透测试呢?
其实,这就好比让开发人员自己做测试一样,虽说他们对自己一手开发出来的软件产品再熟悉不过了,但却也是最难测出漏洞的。因为,开发人员的惯性思维,会使得他们在面对很多的潜在问题时,都误以为这不是问题的,所以我们需要引入独立的测试人员。
同样的道理,面对自己开发的系统,开发人员总是习惯性地去处理比较容易出现安全漏洞的地方,而对于一些隐藏的、不容易被发现的漏洞,却很难发现。
另外,**除了惯性思维之外,开发人员通常并不是安全领域的专家,因此往往会缺少专业的安全知识,不了解常用的系统攻击手段,从而导致他们并不能对安全相关的场景进行充分、客观的测试。**
这里,为了便于理解,我们可以将软件系统比喻成一座房子。
当房子建好后,我们会为其配备防盗门、防盗窗,甚至是安全警报器等,这时我们自认为这个房子足够安全了。但,我们永远都不知道,意图侵入者会使用什么样的方式找到漏洞从而攻克我们布置的安全防线。
所以,为了保证这座房子足够安全,我们会考虑聘请外部的安全专家来进行一系列的检测。比如,检测防盗门是否足够牢固、门锁是否容易被破坏、报警器是否在发生异常时能够正常发出警报、窗户和通道是否容易被侵入,或者从根本上判定我们所布下的安全防线,在安全机制上是否存在系统性的问题,以及需不需要更新等等。
我们甚至可以找人模拟入侵这座房子,在这个模拟过程中,由其发现这所房子是否还存在安全漏洞,以此验证房子真实的安全性。
那么,这个由外部的安全专家验证房子安全性的过程,便可以说是对这座房子进行渗透测试的过程。其中,这个房子便是我们的软件系统;而我们为验证房子安全性采取的这一系列方法,就是我们所说的安全渗透测试。
## 渗透测试的常用方法
那么,安全渗透测试应该怎样进行呢?在这里,我总结了渗透测试的五种常用测试方法,包括:
- 有针对性的测试;
- 外部测试;
- 内部测试;
- 盲测;
- 双盲测试。
接下来,我们就一起看看,具体每种测试方法,要如何开展吧。
1. **有针对性的测试**
有针对性的测试,是由公司内部员工和专业渗透测试团队共同完成的。其中,公司内部员工不仅要负责提供安全测试所需要的基础信息,同时也要负责业务层面的安全测试;而专业渗透测试团队,则更多关注业务以外的、更普适的安全测试。
有针对性的测试,属于研发层面的渗透测试。参与这类测试的人员,可以得到被测系统的内部资料,包括部署信息、网络信息、详细架构设计,甚至是产品代码。
有时,我们也把这种测试方法叫作“开灯”测试。之所以称为“开灯”测试,是因为有针对性的测试,是在测试人员完全了解系统内部情况的前提下开展的,有区别于外部人员完全不知道系统内部细节而进行的渗透测试。
1. **外部测试**
外部测试是针对外部可见的服务器和设备包括域名服务器DNS、Web服务器或防火墙、电子邮箱服务器等等模拟外部攻击者对其进行攻击检查它们是否能够被入侵以及如果被成功入侵了会被入侵到系统的哪一部分、又会泄露多少资料。
一般情况下,外部测试是由内部的测试人员或者专业渗透测试团队,在假定完全不清楚系统内部情况的前提下开展的。
1. **内部测试**
内部测试是由测试工程师模拟内部人员,在内网(防火墙以内)进行攻击,因此测试人员会拥有较高的系统权限,也能够查看各种内部资料,目的是检查内部攻击可以给系统造成什么程度的损害。
所以,内部测试是为了防止系统的内部员工对系统进行内部攻击,同时以此来制定系统内部员工的权限管理策略。
1. **盲测**
盲测,指的是在严格限制提供给测试执行人员或团队信息的前提下,由他们来模拟真实攻击者的行为和上下文。通常,测试人员可能只被告知被测系统公开的信息,而对系统细节以及内部实现一无所知。
因为这种类型的测试可能需要相当长的时间进行侦察,所以代价会相对昂贵。而且,这类测试的效果,将在很大程度上取决于测试人员的技术水平。一般来讲,盲测是由专业渗透测试团队在测试后期开展的,通常会借助很多黑客攻击工具。
可以想象,如果测试人员拥有专业黑客的技术水平,同时结合各类渗透和黑客工具,一定能发现安全漏洞;但是,如果测试人员并不具备专业的安全测试以及系统攻击知识,那么可想而知,他们能够发现的问题就非常有限了。
1. **双盲测试**
双盲测试比盲测更进一步,也叫作“隐秘测试”。
在这类测试中,不光测试人员对系统内部知之甚少,而且被测系统内部也只有极少数人知道正在进行安全测试。因此,双盲测试可以反映软件系统最真实的安全状态,能够有效地检测系统在正常情况下,对安全事件的监控和处理能力是否合格。
因此,双盲测试可以用于测试系统以及组织的安全监控和事故识别能力,及其响应过程。一般来说,双盲测试一般是由外部的专业渗透测试专家团队完成,所以实际开展双盲测试的项目并不多。
## 执行渗透测试的步骤
了解了渗透测试的常用方法那么到底要怎样具体开展呢现在我就和你分享一下开展渗透测试的5个主要步骤
**第一步:规划和侦察**。这一步包含了定义测试的范围和目标、初步确定要使用的工具和方法、明确需要收集的情报(例如,网络和域名,邮件服务器),以更好地了解目标的工作方式及其潜在的安全漏洞。
**第二步:安全扫描**。安全扫描包括静态分析和动态分析两个阶段。
<li>
静态分析阶段是通过扫描所有代码来估计其运行时的方式。这里我们可以借助一些工具来一次性地扫描所有代码。目前主流工具有Fortify SCA和Checkmarx Suite。
</li>
<li>
动态分析阶段,则是在代码运行时进行扫描。这样的扫描更能真实反映程序的行为,可以实时提供应用程序的运行时视图,比静态扫描更准确、实用。
</li>
**第三步:获取访问权限**。在这一步测试人员将模拟黑客对应用程序进行网络攻击例如使用SQL注入或者XSS跨站脚本攻击等以发现系统漏洞。然后利用找到的漏洞通过升级自己的权限、窃取数据、拦截流量等方式了解其可能对系统造成的损害。
至于到底什么是SQL注入什么是XSS跨站脚本攻击你可以自行查阅一些资料也可以给我留言一起讨论。
**第四步:维持访问权限**。这个阶段的目的是,查看被发现的漏洞是否可以长期存在于系统中,如果漏洞能够被持久化,那么在很长的一段时间内入侵者都可以对系统进行深入访问或进行破坏。
这个阶段模仿的是高级持续性威胁。这类威胁,通常在系统中可以存在数月之久,入侵者可以借此获取组织内较高级别的敏感数据。
**第五步:入侵分析**。完成以上的四步之后,我们就要分析得到的结果了。通常情况下,我们需要将测试结果汇总成一份详尽的测试报告,并详细说明:
<li>
可以被利用的特定漏洞;
</li>
<li>
利用该漏洞的具体步骤;
</li>
<li>
能够被访问的敏感数据;
</li>
<li>
渗透测试人员能够在系统中不被侦测到的存在时间。
</li>
专业的安全人员会分析这些信息以指导和帮助我们配置企业的WAF(Web Application Firewall),同时提供对其他应用程序的安全解决方案,以修补安全漏洞并防范未来的恶意攻击。
## 渗透测试的常用工具
目前在实际的渗透测试中我们通常会使用大量的工具来完成测试。为此我挑选了Nmap、Aircrack-ng、sqlmap、Wifiphisher、AppScan这五种常用工具和你分享一下它们的功能以及适用的场景。
这里需要特别注意的是,这些工具本身就具有黑客属性,所以很多杀毒软件会阻止该类软件的运行。同时,你也一定不要在非官方的网站下载和使用这类工具,以防被意图不轨的人预先注入了危险的攻击点,请务必小心。
1. **Nmap**
Nmap是进行主机检测和网络扫描的重要工具。它不仅可以收集信息还可以进行漏洞探测和安全扫描从主机发现、端口扫描到操作系统检测和IDS规避/欺骗。
Nmap这类工具是渗透测试过程中最先要用到的用来获取后续渗透测试过程中需要用到的系统基本信息比如IP和端口等。
同时Nmap适用于各大操作系统包括Windows、Linux、macOS等因此是一款非常强大、实用的安全检测工具。
1. **Aircrack-ng**
Aircrack-ng是评估Wi-Fi网络安全性的一整套工具。它侧重于Wi-Fi安全的领域主要功能有网络侦测、数据包嗅探、WEP和WPA/WPA2-PSK破解。
Aircrack-ng可以工作在任何支持监听模式的无线网卡上并嗅探802.11a、802.11b、802.11g的数据。
Aircrack-ng的执行是通过命令行或者脚本文件的方式并且可以运行在Linux和Windows操作系统上。它的典型应用场景主要包括数据包注入重播攻击、解除身份验证、虚假接入点等也可以用于破解WEP和WPA PSK。
1. **sqlmap**
sqlmap是一种开源的基于命令行的渗透测试工具。它能够自动进行SQL注入和数据库接入并且支持所有常见并广泛使用的数据库平台包括Oracle、MySQL、Microsoft SQL Server、SQLite、Microsoft Access、IBM DB2、FireBird、Sybase和SAP Max DB等使用的SQL注入技术也几乎涵盖了所有的攻击手段。
如果你不采用AppScan这类全面的商用安全测试工具我的建议是通过sqlmap来确保系统数据库的安全性。
1. **Wifiphisher**
Wifiphisher是一种恶意接入点工具可以对Wi-Fi网络进行自动钓鱼攻击。
渗透测试执行人员可以通过Wifiphisher执行有针对性的Wi-Fi关联攻击轻松实现无线客户端的渗透测试。
Wifiphisher还可以用于对连接的客户端进行受害者定制的网络钓鱼攻击用来获取凭证例如从第三方登录页面或WPA/WPA2预共享密钥或用恶意软件感染受害者站点。
1. **AppScan**
AppScan是IBM公司的一款企业级商业Web应用安全测试工具采用的是黑盒测试可以扫描常见的Web应用安全漏洞。
AppScan的工作原理是
- 首先,从起始页爬取站下所有的可见页面,同时测试常见的管理后台;
- 然后利用SQL注入原理测试所有可见页面是否在注入点和跨站脚本攻击的可能
- 同时检测Cookie管理、会话周期等常见的Web安全漏洞。
AppScan的功能十分强大几乎涵盖了目前所有已知的攻击手段而且攻击库还在不断地升级更新。此外从AppScan的扫描结果中我们不仅可以看到扫描的漏洞还提供了详尽的漏洞原理、修改建议、手动验证等。
可以说AppScan是目前最完美的渗透测试商用解决方案但是其最大的问题在于其价格昂贵一般只有中大型的企业才会购买使用。
## 渗透测试的收益
现在,你已经清楚了开展渗透测试的必要性,也大致清楚了具体要如何开展渗透测试。那么,接下来,为了让你对开展渗透测试的信心更足,我再为你总结一下它能解决的问题:
通过渗透测试,公司可以识别出主要漏洞,并决定修补漏洞的优先级,同时合理分配系统补丁安装的时间,以确系统环境的安全性。
避免了安全漏洞,也就是避免了不必要的损失。因为,从安全漏洞中恢复出来,公司往往要花费巨大的代价去补救公司和客户的损失,甚至可能因此吃官司。而,渗透测试能够很好地避免这类问题,帮助公司树立良好的企业形象,因此赢得更高的信任度。
总的来说,我们应该按需选择适合自己产品的渗透测试方案,期间需要考虑到产品安全隐患和执行渗透测试的成本之间的平衡。
## 总结
在今天的这次分享中,我介绍了与渗透测试相关的知识点。
首先,渗透测试是指由专业安全人员模拟黑客,从其可能入侵的位置对系统进行攻击测试,以达到在真正的黑客攻击之前找到隐藏的安全漏洞,从而保护系统安全的目的。
然后,我根据发起渗透测试的位置以及对系统信息的掌握程度,将渗透测试分为了有针对性的测试、外部测试、内部测试、盲测和双盲测试这五种。
接着我和你分享了开展渗透测试的5个步骤分别包括了规划和侦察、安全扫描、获取访问权限、维持访问权限以及入侵分析。
最后我给你汇总了五款常用的渗透测试工具其中功能最强大的要数IBM的AppScan了但是其价格比较昂贵适用于中大型企业。而关于如何选择适合自己的渗透测试方案我的建议还是要综合考虑产品安全隐患和执行渗透测试的成本。
## 思考题
你所在的公司或者团队是否开展了渗透测试?你们会使用哪些渗透测试工具呢?
感谢你的收听,欢迎你给我留言一起讨论。

View File

@@ -0,0 +1,166 @@
<audio id="audio" title="47 | 用机器设计测试用例:基于模型的测试" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/e1/f0/e14faed5001767c0193a3d65658272f0.mp3"></audio>
你好,我是茹炳晟。今天我和你分享的主题是:用机器设计测试用例之基于模型的测试”。
我在前面4篇文章中和你分享的探索式测试、测试驱动开发TDD、精准测试以及渗透测试的内容你是否已经掌握了呢有没有尝试将这些比较新的理念用到你的工程项目中呢如果你在应用的过程中遇到了任何问题也欢迎给我留言一起讨论。
那么,现在我们就正式开始测试新技术系列的最后一个话题:基于模型的测试。
可以说,软件测试是一款软件产品质量的最后一道防线,是产品上线前必不可少、最重要的一个环节。每一款高质量的软件产品背后,都蕴含了大量的测试工作。而且,这些测试工作很可能是整个软件开发过程中最昂贵、劳动最密集的工作。
虽说从最简单的功能性黑盒测试,到涉及定理证明的复杂测试,已经有很多种方法可以帮助我们提高测试的可靠性和有效性。但是,在设计测试用例的过程中,总还是存在着这样那样的问题,使得软件测试的结果没那么理想。
为此我们新引入了基于模型的测试即Model-Based-Testing简称MBT。
MBT是自动化测试的一个分支。它是将测试用例的设计依托于被测系统的模型并基于该模型自动生成测试用例的技术。其中这个被测系统的模型表示了被测系统行为的预期也可以说是代表了我们对被测系统的预期。
从质量保证的角度来看我们可以制定测试内容但却无法保证测试会覆盖所有可能的组合。而MBT则允许软件开发人员和测试人员只关注建立系统的正确性以及模型的规范性再通过专门的MBT工具根据不同的测试用例设计策略从系统模型生成可靠的测试用例。
那么MBT的原理是什么而什么样的应用又适合进行MBT呢接下来我就重点为你回答这两个问题。
## MBT的基本原理
MBT的基本原理是通过建立被测系统的设计模型然后结合不同的算法和策略来遍历该模型以此生成测试用例的设计。
我用下面的一张图片为你描述了MBT的过程
<img src="https://static001.geekbang.org/resource/image/08/f9/08c3605390ca20c46b63e92f29f375f9.png" alt="" />
如图1所示开发者首先根据产品需求或者说明来构建模型然后结合测试对象生成测试用例测试用例针对测试对象执行完之后生成测试报告比对测试结果。
接下来,我以简单的登录系统为例,和你说明如何建模。
当用户访问网站时,网站需要识别用户是否已经登录:
- 如果已经是登录状态,则让用户进入,结束这一分支;
- 如果用户还没有登录,那么页面需要返回登录框给用户。用户在登录框输入用户名和密码后,由后台服务验证用户名和密码是否正确,如果通过验证,则用户登录成功,结束分支;否则,返回错误信息,并再次返回登录框供用户登录。
根据这个逻辑,我们可以建模如下:
<img src="https://static001.geekbang.org/resource/image/7f/95/7fb2ceb31c81046261d59a3c2226a195.png" alt="" />
至此,我们就完成对这个登录系统的建模工作了。然后,我们通过具象化被测产品的需求行为,再通过工具来遍历模型中的各个路径,就可以得到我们需要的测试用例了。
所以执行MBT的过程就好比你把软件系统的设计画为了一张由节点和边构成的数据结构意义上的“图”然后通过一定的算法比如深度遍历或者广度遍历来尽可能完整覆盖图中全部的可能路径的过程。
而根据被测系统的特点我们可以创建不同类型的模型完成MBT。接下来我们就一起看看有哪些常用的MBT模型吧。
## 常用模型简介
根据被测系统本身的特点我们常用的模型主要有限状态机、状态图以及UML三种。其中有限状态机和状态图比较适合于用状态或者事件驱动的系统而UML比较适合于靠业务流程驱动的系统。
**第一,有限状态机。**
有限状态机可以帮助测试人员根据选中的输入来评估输出,不同的输入组合对应着不同的系统状态。
在登录系统这个例子中,员工在未登录时的状态是“未登录”,一旦登录成功状态就会变为“已登录”。在已登录的状态下,员工可以访问各类资源,使用系统内的工具。
**第二,状态图。**
状态图是有限状态机的延伸,用于描述系统的各种行为,尤其适用于复杂且实时的系统。
状态图有一定数量的状态系统的行为可以以事件的方式来驱动状态的变化。比如缺陷管理工具中出现了缺陷其初始状态为“new”缺陷被开发人员修复后就必须将其改为“Fixed”但是如果此时测试人员发现缺陷并未修复或者只是部分修复时则需将状态更改为“Reopen”重新打开
状态图的设计方式,要求为每个不同的状态创建一个事件。
**第三UML。**
UML即统一建模语言是一种标准化的通用建模语言。UML有自己定义的图形库里面包含了丰富的图形用以描述系统、流程等。
UML可以通过创建可视化模型来描述非常复杂的系统行为。
当我们完成被测系统的建模工作后,接下来就要将模型转化为可执行的测试用例了。这个转换过程,需要借助工具来完成。
因为不同领域的产品风格迥异其使用的自动化框架和编程语言也各不相同所以我们需要花费一些精力去寻找与自己产品匹配的MBT工具。其实在很多情况下我们还需要根据产品特点去自行开发和定制工具。
## MBT工具简介
这里我为你罗列了一些常见的MBT工具包括BPM-X、fMBT、GraphWalker。在这里我为你简单介绍这些工具的目的是让你可以对MBT工具本身有个感性的认识让你知道此类工具的应用场景和上下文。至于说如何来选择使用这些工具这在很大程度上取决于被测系统的特点。
**第一BPM-X**
BPM-X根据不同的标准比如语句、分支、路径、条件从业务流程模型创建测试用例。
它还可以从多个建模工具导入模型并可以将测试用例导出到Excel、HP Quality Center等。这个工具适用于业务流程比较清晰直观的场景。
**第二fMBT**
fMBT是一组免费的、用于全自动测试生成和执行的工具也是一组支持高水平测试自动化的实用程序和库主要被应用在GUI测试中。
fMBT包括用于多平台GUI测试的Python库用于编辑、调试、运行和记录GUI测试脚本的工具以及用于编辑和可视化分析测试模型和生成的测试工具。
**第三GraphWalker**
GraphWalker以有向图的形式读取模型主要支持FSM、EFSM模型。它读取这些模型然后生成测试路径。GraphWalker除了适用于GUI测试外更适合于多状态以及基于事件驱动的状态转换的后台系统。
另外GraphWalker还支持从有限状态机中生成测试用例。
除此之外市面上还有很多MBT测试工具比如GSL、JSXM、MaTeLo、MBT Suite等。这里就不再一一介绍了你可以自行百度了解它们的特点和适用场景从而选取合适自己的工具。
## MBT的优势
其实MBT并不能算是一种新颖的测试技术早在七八年前就已经被提了出来并且试图应用于软件产品的测试工作中。但是MBT在很长一段时间内却一直停留在概念阶段主要原因是一直没有普适的工具支持所以很少有成功实施的实际案例。同时业界一直以来都缺乏高效的测试用例设计生成策略所以虽然大家都能看到MBT的优势但能在实际项目中应用执行的却是寥寥无几。
与传统测试相比MBT的优点如下
<li>
**测试用例的维护更轻松**。由于测试用例是基于被测系统的模型生成的,因此我们只需维护好模型即可,而无需关注测试用例的细节。
</li>
<li>
**软件缺陷发现得更早**。由于我们在构建被测系统模型的过程中,已经对被测系统有了比较全面的理解,加之要根据系统需求/说明完成建模过程,所以我们可以在早期建模时发现被测系统可能存在的明显缺陷,而不用等到执行了大量的测试用例以后才发现这些缺陷。
</li>
<li>
**测试自动化的水平更高**。由于MBT只需建好模型便可以自动生成测试用例所以不再需要人工编写测试文档。而更高级的应用甚至可以直接生成可以直接执行的自动化测试脚本。
</li>
<li>
<p>**测试覆盖率变得更高,使得彻底的测试(即:穷尽测试)成为了可能**。由于我们需要做的只是正确、详尽地用模型描述被测系统而生成测试用例完全由MBT工具实现所以这就避免了人工设计测试用例时的思维局限性能够有效地提高测试覆盖率让彻底的测试变为可能。<br />
当然是否有必要开展彻底的测试还是要由风险决定。这里的风险指的是由于漏测导致产品问题对业务的影响程度。MBT只是从技术上提供了可能性。</p>
</li>
<li>
**基于模型间接维护测试用例的方式更高效**。在传统测试中如果被测系统的流程或者功能发生了变化我们需要耗费大量的人力和时间成本去重新设计与之匹配的测试用例。而在MBT中我们只需要更新被测系统的模型即可剩下的测试用例生成工作可以由MBT工具自动完成。
</li>
## MBT的劣势
虽然MBT相对于传统测试有很多优点但它也不是完美的测试方案。在实际开展MBT时我们往往需要应对很多挑战并克服很多困难。所以到现在为止MBT并没有被广泛使用于软件测试领域。
在这里我总结了开展MBT的三大难点
<li>
**学习成本较高**。MBT要求开发人员和测试人员都精通建模这就需要一定的培训成本需要让开发人员去学习测试的技能让测试人员去学习建模概念。这其中还牵涉到建模工具的选择以及学习等成本。
</li>
<li>
<p>**使用MBT的初期投资较大**。在很多情况下我们并不能找到适合自己产品的建模工具而需要自行创建MBT工具。<br />
在自行定制MBT工具时我们要考虑到这个工具必须是可扩展的并且能够处理复杂的测试逻辑提供足够高的测试覆盖率因此刚开始的工具建设就需要花费大量时间和精力。<br />
更糟糕的情况是,当工具建好后,我们却发现它并不能满足所有的建模需求,因此还要在建模的同时对工具进行微调。而,这种微调工作的难度,也比较大。</p>
</li>
<li>
<p>**早期根据模型生成测试用例的技术并不是非常成熟**。很多时候只是根据图论的算法来遍历模型这就会导致生成的很多测试用例在业务上根本没有任何实际意义也因此阻碍了MBT在实际项目中的落地。<br />
不过好在近一两年来基于人工智能AI生成测试用例的概念不断成熟所以将基于AI的测试用例生成和MBT相结合将会是接下来的一个发展方向。</p>
</li>
总的来说MBT和传统测试各有优劣。所以测试的方法多种多样MBT只是其中的一种。
如果一个应用的任何组件都可以通过模型来模拟、通过驱动程序来驱动并可以通过测试结果来比较的话那么这个应用就是MBT的最佳候选者。
如果我们的产品特征符合开展MBT的要求并且团队各方面的条件都支持使用MBT时我们便可以尝试用这种方法来改革我们的测试方式。尤其是将MBT结合基于AI的测试用例生成技术将可以大大加速MBT产业应用的步伐。
但是不管是否采用MBT开发人员或测试人员在接触到一款软件产品时首先都会有一个心理建模的过程自己先去理解并在脑中勾勒出系统的功能结构和流程。其实这些内容很容易就可以转换成实际的模型也就为MBT创造了条件。
## 总结
今天我和你分享了MBT的基本概念和方法。
MBT是一种基于被测系统的模型由工具自动生成测试用例的软件测试技术。所以这也就决定了MBT相对于传统测试技术可以在测试用例维护、软件缺陷发现时机、测试自动化水平以及测试覆盖率等方面有其独到的优势。
但同时这也使得MBT相对于传统软件测试在初期阶段投入较大学习应用的成本较高。也正因为如此MBT概念虽然已经提出了七八年的时间但却并没有被广泛应用于软件测试领域。
所以关于是否要在自己的项目中开展MBT我们需要综合考虑项目本身的特点和人员的技术水平以此决定是否有必要开展MBT。
## 思考题
假如你要在项目中开展MBT你会如何判断你的项目是否适合采用MBT以及你认为会遇到哪些问题可能会阻碍MBT的开展呢
感谢你的收听,欢迎你给我留言一起讨论。