漂亮的测试. By Alberto Savoia 代码之美 第七章 史际帆

Similar documents
Previous on Computer Networks Class 18. ICMP: Internet Control Message Protocol IP Protocol Actually a IP packet

ICP Enablon User Manual Factory ICP Enablon 用户手册 工厂 Version th Jul 2012 版本 年 7 月 16 日. Content 内容

计算机组成原理第二讲 第二章 : 运算方法和运算器 数据与文字的表示方法 (1) 整数的表示方法. 授课老师 : 王浩宇

AvalonMiner Raspberry Pi Configuration Guide. AvalonMiner 树莓派配置教程 AvalonMiner Raspberry Pi Configuration Guide

PCU50 的整盘备份. 本文只针对操作系统为 Windows XP 版本的 PCU50 PCU50 启动硬件自检完后, 出现下面文字时, 按向下光标键 光标条停在 SINUMERIK 下方的空白处, 如下图, 按回车键 PCU50 会进入到服务画面, 如下图

实验三十三 DEIGRP 的配置 一 实验目的 二 应用环境 三 实验设备 四 实验拓扑 五 实验要求 六 实验步骤 1. 掌握 DEIGRP 的配置方法 2. 理解 DEIGRP 协议的工作过程

Logitech G302 Daedalus Prime Setup Guide 设置指南

三 依赖注入 (dependency injection) 的学习

如何查看 Cache Engine 缓存中有哪些网站 /URL

<properties> <jdk.version>1.8</jdk.version> <project.build.sourceencoding>utf-8</project.build.sourceencoding> </properties>

Understanding IO patterns of SSDs

OTAD Application Note

Chapter 1 (Part 2) Introduction to Operating System

组播路由 - MSDP 和 PIM 通过走

The Design of Everyday Things

上汽通用汽车供应商门户网站项目 (SGMSP) User Guide 用户手册 上汽通用汽车有限公司 2014 上汽通用汽车有限公司未经授权, 不得以任何形式使用本文档所包括的任何部分

VAS 5054A FAQ ( 所有 5054A 整合, 中英对照 )

Command Dictionary CUSTOM

Software Engineering. Zheng Li( 李征 ) Jing Wan( 万静 )

#MDCC Swift 链式语法应 用 陈乘

Microsoft RemoteFX: USB 和设备重定向 姓名 : 张天民 职务 : 高级讲师 公司 : 东方瑞通 ( 北京 ) 咨询服务有限公司

Wireless Presentation Pod

Chapter 7: Deadlocks. Operating System Concepts 9 th Edition

Skill-building Courses Business Analysis Lesson 3 Problem Solving

Safe Memory-Leak Fixing for C Programs

测试基础架构 演进之路. 茹炳晟 (Robin Ru) ebay 中国研发中心

Technology: Anti-social Networking 科技 : 反社交网络

第二小题 : 逻辑隔离 (10 分 ) OpenFlow Switch1 (PC-A/Netfpga) OpenFlow Switch2 (PC-B/Netfpga) ServerB PC-2. Switching Hub

朱晔和你聊 Spring 系列 S1E2: SpringBoot 并不神秘

Triangle - Delaunay Triangulator

Build a Key Value Flash Disk Based Storage System. Flash Memory Summit 2017 Santa Clara, CA 1

Apache Kafka 源码编译 Spark 大数据博客 -

Chapter 11 SHANDONG UNIVERSITY 1

学习沉淀成长分享 EIGRP. 红茶三杯 ( 朱 SIR) 微博 : Latest update:

CloudStack 4.3 API 开发指南!

Declaration of Conformity STANDARD 100 by OEKO TEX

Spark Standalone 模式应用程序开发 Spark 大数据博客 -

Air Speaker. Getting started with Logitech UE Air Speaker. 快速入门罗技 UE Air Speaker. Wireless speaker with AirPlay. 无线音箱 (AirPlay 技术 )

XML allows your content to be created in one workflow, at one cost, to reach all your readers XML 的优势 : 只需一次加工和投入, 到达所有读者的手中

Ganglia 是 UC Berkeley 发起的一个开源集群监视项目, 主要是用来监控系统性能, 如 :cpu mem 硬盘利用率, I/O 负载 网络流量情况等, 通过曲线很容易见到每个节点的工作状态, 对合理调整 分配系统资源, 提高系统整体性能起到重要作用

NyearBluetoothPrint SDK. Development Document--Android

nbns-list netbios-type network next-server option reset dhcp server conflict 1-34

U-CONTROL UMX610/UMX490/UMX250. The Ultimate Studio in a Box: 61/49/25-Key USB/MIDI Controller Keyboard with Separate USB/Audio Interface

1. DWR 1.1 DWR 基础 概念 使用使用 DWR 的步骤. 1 什么是 DWR? Direct Web Remote, 直接 Web 远程 是一个 Ajax 的框架

Multiprotocol Label Switching The future of IP Backbone Technology

OpenCascade 的曲面.

CHINA VISA APPLICATION CONCIERGE SERVICE*

Supplementary Materials on Semaphores

Keygen Codes For Photoshop Cs6 ->>> DOWNLOAD

Packaging 10Apr2012 Rev V Specification MBXL HSG 1. PURPOSE 目的 2. APPLICABLE PRODUCT 适用范围

NetScreen 概念与范例. ScreenOS 参考指南 第 7 卷 : 虚拟系统. ScreenOS 编号 SC 修订本 E

A Benchmark For Stroke Extraction of Chinese Characters

Compile times - assert macros

2. Introduction to Digital Media Format

PTZ PRO 2. Setup Guide 设置指南

IPC 的 Proxy-Stub 设计模式 ( c)

Machine Vision Market Analysis of 2015 Isabel Yang

我们应该做什么? 告知性分析 未来会发生什么? 预测性分析 为什么会发生 诊断性分析 过去发生了什么? 描述性分析 高级分析 传统 BI. Source: Gartner

测试 SFTP 的 问题在归档配置页的 MediaSense

H3C CAS 虚拟机支持的操作系统列表. Copyright 2016 杭州华三通信技术有限公司版权所有, 保留一切权利 非经本公司书面许可, 任何单位和个人不得擅自摘抄 复制本文档内容的部分或全部, 并不得以任何形式传播 本文档中的信息可能变动, 恕不另行通知

Computer Networks. Wenzhong Li. Nanjing University

libde265 HEVC 性能测试报告

Smart Services Lucy Huo (Senior Consultant, UNITY Business Consulting) April 27, 2016

新一代 ODA X5-2 低调 奢华 有内涵

Logitech ConferenceCam CC3000e Camera 罗技 ConferenceCam CC3000e Camera Setup Guide 设置指南

Command Dictionary -- DAMSTAB

Presentation Title. By Author The MathWorks, Inc. 1

DEV Office 客户端开发增强

Oracle 一体化创新云技术 助力智慧政府信息化战略. Copyright* *2014*Oracle*and/or*its*affiliates.*All*rights*reserved.** *

EBD EBD. end

梁永健. W K Leung. 华为企业业务 BG 解决方案销售部 CTO Chief Technology Officer, Solution Sales, Huawei

TBarCode OCX Microsoft ActiveX compliant Barcode Control

Parallel Programming Principle and Practice Lecture 7

Chapter 2: Java OO II. Yang Wang wyang AT njnet.edu.cn

IEEE 成立于 1884 年, 是全球最大的技术行业协会, 凭借其多样化的出版物 会议 教育论坛和开发标准, 在激励未来几代人进行技术创新方面做出了巨大的贡献, 其数据库产品 IEL(IEEE/IET Electronic Library)

操作系统原理与设计. 第 13 章 IO Systems(IO 管理 ) 陈香兰 2009 年 09 月 01 日 中国科学技术大学计算机学院

密级 : 博士学位论文. 论文题目基于 ScratchPad Memory 的嵌入式系统优化研究

Safety Life Cycle Model IEC61508 安全生命周期模型 -IEC61508

智能终端与物联网应用 课程建设与实践. 邝坚 嵌入式系统与网络通信研究中心北京邮电大学计算机学院

display portal server display portal user display portal user count display portal web-server

PubMed 简介. PubMed 是美国国立医学图书馆 (NLM) 所属的国家生物技术信息中心 (NCBI) 开发的因特网生物医学信息检索系统

Lesson 20 Microcontroller Integrated Circuit With Read Only Memory

Decode Zend. Darkness/Airsupply

TW5.0 如何使用 SSL 认证. 先使用 openssl 工具 1 生成 CA 私钥和自签名根证书 (1) 生成 CA 私钥 openssl genrsa -out ca-key.pem 1024

Outline. Motivations (1/3) Distributed File Systems. Motivations (3/3) Motivations (2/3)

SHANDONG UNIVERSITY 1

Color LaserJet Pro MFP M477 入门指南

菜鸟调错 原文出处 : 菜鸟调错作者 : 刘水镜 本系列文章经作者授权在看云整理发布, 未经作者允许, 请勿转载! 菜鸟调错 分享开发中遇到的各种各样的错误, 以及解决方法, 让更多的人少走同样的弯路 本文档使用看云构建

China Next Generation Internet (CNGI) project and its impact. MA Yan Beijing University of Posts and Telecommunications 2009/08/06.

TDS - 3. Battery Compartment. LCD Screen. Power Button. Hold Button. Body. Sensor. HM Digital, Inc.

Bi-monthly report. Tianyi Luo

Murrelektronik Connectivity Interface Part I Product range MSDD, cable entry panels MSDD 系列, 电缆穿线板

计算机科学与技术专业本科培养计划. Undergraduate Program for Specialty in Computer Science & Technology

武汉大学 学年度第 1 学期 多核架构及编程技术 试卷(A)

S 1.6V 3.3V. S Windows 2000 Windows XP Windows Vista S USB S RGB LED (PORT1 PORT2 PORT3) S I 2 C. + 表示无铅 (Pb) 并符合 RoHS 标准 JU10 JU14, JU24, JU25

[ 电子书 ]Spark for Data Science PDF 下载 Spark 大数据博客 -

Epetra_Matrix. August 14, Department of Science and Engineering Computing School of Mathematics School Peking University

Specifications 产品规格书 USB-D M X 控制器. DESCRIPTION: USB-D M X Control 产品名称 : MODEL NO: USB-DMX512-CONTROL 产品型号 :

Congestion Control Mechanisms for Ad-hoc Social Networks 自组织社会网络中的拥塞控制机制

Jbuilder 2007 开发 EJB3.0 Entity 罗代均 ldj_work#126.com 2007 年 8 月

Transcription:

漂亮的测试 代码之美 第七章 By Alberto Savoia 史际帆

作者简介 :Alberto Savoia Alberto Savoia is co-founder and CTO of Agitar Software. Before Agitar, he was Senior Director of Engineering at Google; prior to that he was the Director of Software Research at Sun Microsystems Laboratories. Alberto s passion and main body of work and research is in the area of software development technology in particular, tools and technology to help programmers test and verify their own code during the design and development phase. Alberto Savoia 是 Agitar 软件公司创始人之一和 CEO 在创建 Agitar 之前, 它是 Google 的高级工程主管 ; 在这之前, 他还是 Sun Microsystems 实验室软件研究中心的主管 其主要研究领域是软件开发技术 尤其是那些帮助程序员在设计和开发阶段尽心测试和代码验证的工具和技术

何以谓 漂亮的代码? 实现所要求的功能 优雅 简洁 漂亮的测试 呢? 测试是对于代码正确与否的检验, 当然也应该漂亮 而测试天生带有组合性与试探性, 单一测试的漂亮并不一定组合起来还漂亮 例如 : 代码中的每一条 if 语句至少需要两个测试 : 一个测试条件表达式为真, 另一个测试其为假的情况 而 if ( a b c ) 则理论上需要八个测试 再考虑到循环中的异常, 多个输入参数, 对外部代码的依赖, 不同的软硬件平台等, 所需测试的数量和类型将大大增加 所以除最简单外, 任何代码都需要一组测试而不是一个 其中每一个测试专注于检查代码的一个特定方面 就像球队中不同球员有不同职责, 负责不同区域一样

测试一般是逐步建立的, 并逐步来验证所测试代码的正确与高效性 测试的漂亮 : 简单 迅速 揭示出代码的本质找出逻辑错误, 发现结构和设计上的问题, 使代码更健壮, 更有可读性 测试具有深度与广度深入彻底 覆盖无遗, 增强开发者信心 讨论漂亮的测试, 必先寻找足够生动有趣, 常出 bug 而本身简单漂亮的代码 作者找到了 Jon Bentley Programming Pearls 书中的二分法查找算法

二分查找 Bug: 如果 low 和 high 的和大 Here is a Java implementation with the infamous bug: 于 Integer.MAX_VALUE(Java public static int buggybinarysearch(int[] a, int target) { 中是 2 31-1), 计算将会溢出 int low = 0; 使之成为一个负数 int high = a.length - 1; while (low <= high) { 包含 target 则返回其位置 ( 可以提到外面 ) int mid = (low + high) / 2; int midval = a[mid]; if (midval < target) low = mid + 1; else if (midval > target) high = mid - 1; else return mid; 不含 target return -1; 则返回 -1 修改方法 : 1. 使用减法 int mid = low + ((high - low) / 2); 2. 无符号位移运算 int mid = (low + high) >>> 1; Jon Bentley 向学生们讲述此算法 : 在一个包含 t 的数组中, 通过将范围中间的元素与 t 进行比较并丢弃一半的范围, 一直持续直到 t 被发现或范围为空 但他又记述了怎样在多年时间中让上百个专业程序员来实现, 每次给他们 2 小时并随意选择高级语言, 但令人惊讶的是大约只有 10% 的专业程序员正确实现了二分查找 Donald Knuth 在 Sorting and Searching 中指出 : even though the first binary search was published in 1946, it took 12 more years for the first binary search without bugs to be published.

二分查找 那是一个在好几十年时间里, 连一些最好的程序员都抓不到的 Bug, 它教导我们 : 哪怕是最简单的一段代码, 要写正确也并非易事, 更别提我们现实世界中的系统 他们跑在大段大段的复杂代码之上 但是这段代码在修改之后还是有问题, 或许不是 Bug, 但还有可以而且应该被修改的地方 这些修改可以使代码不仅更加健壮, 而且可读性, 可维护性, 可测试性都会比原来好

Java 程序测试的框架 :JUnit Kent Beck 和 Erich Gamma 设计 简单而宏伟的目标 : 使程序开发者更容易去做他们本来就应该做的事情 测试自己的代码 Never in the field of software development was so much owed by so many to so few lines of code. 软件开发领域中从此之前从未有过这样的事情 : 很少的几行代码对大量代码起了重要的作用 Martin Fowler

Java 程序测试的框架 :JUnit 框架 : 框架是一个应用程序的半成品 框架提供了可以在应用程序之外共享可复用的公共结构 开发者把框架融入他们自己的应用程序, 并加以扩展, 以满足他们特定的需要 框架和工具包的不同之处在于, 框架提供了一致的结构, 而不仅仅是一组工具类

Java 程序测试的框架 :JUnit 单元测试 : 单元测试测的是独立的一个工作单元 在 Java 应用程序中, 独立的一个工作单元 常常指的是一个方法 ( 但不总是如此 ) 作为对比, 集成测试和接收测试则检查多个组件如何交互 一个工作单元是一项任务, 它不依赖于其他任何任务的完成 1 单元测试可以降低不确定性从而降低风险 2 单元测试可以帮助优化设计 3 单元测试用例可以当文档使用

Java 程序测试的框架 :JUnit 所有的代码都要通过测试 调试代码可能比写代码用的时间还长 手工测试 : 增加记录 查看记录 编辑记录 删除记录 一遍一遍的做测试直到通过, 还有可能出错 如果不喜欢重复工作, 需要自动测试

Java 程序测试的框架 :JUnit 本章中使用的方法 : 1. 当你要测试一样东西时, 为一个方法加上 @org.junit.test; 2. 当你要检查一个值时, 输入 org.junit.assert.*, 然后调用 asserttrue( ), 并传递一个 Boolean 对象, 当测试成功时它为 true 例如 : 测试两个 Money 对象相加 @Test public void simpleadd( ) { Money m12chf= new Money(12, "CHF"); Money m14chf= new Money(14, "CHF"); Money expected= new Money(26, "CHF"); Money result= m12chf.add(m14chf); asserttrue(expected.equals(result));

JUnit 的核心 在 JUnit in action 中使用的是 JUnit3.8.1, 它的写法 : import junit.frame.testcase; public class TestCalculator extends TestCase { public void testadd() { Calculator calculator = new Calculator(); double result = calculator.add(10, 50); assertequals(60, result, 0); 其中 assertequals 为 : static public void assertequals(double expected, double actual, double delta)

七个核心类及接口 : 类 / 接口 JUnit 的核心 作用 Assert TestResult Test 但条件成立时 assert 方法保持沉默, 但若条件不成立就抛出异常 TestResult 包含了测试中发生的所有错误或者失败 可以运行 Test 并把结果传递给 TestResult TestListener TestCase TestSuite 测试中若产生事件 ( 开始 结束 错误 失败 ) 会通知 TestListener TestCase 定义了可以运行与多项测试的环境 ( 或者说是固定设备 ) TestSuite 运行一组 TestCase( 他们可能包含其他 TestSuite), 它是 Test 的组合 BaseTestRunner TestRunner 是用来启动测试的用户界面, BaseTestRunner 是所有 TestRunner 的超类

JUnit 的核心 : Assert 超类型 方法 描述 AssertTrue AssertFalse AssertEquals AssertNotNull AssertNull AssertSame AssertNotSame fail 断言条件为真, 否则抛出 AssertionFailedError 断言条件为假, 否则抛出 AssertionFailedError 断言两对象相等, 否则抛出 AssertionFailedError 断言对象不为 null, 否则抛出 AssertionFailedError 断言对象为 null, 否则抛出 AssertionFailedError 断言两引用指向同一对象, 否则抛出 AssertionFailedError 断言断言两引用指向不同对象, 否则抛出 AssertionFailedError 让测试失败并给出信息

JUnit 的核心 而 TestCase 有 10 个基本方法 整体较复杂 assert <<interface>> test testcase testsuite testresult basetestru nner <<interface>>te stlistener

测试二分查找算法的步骤 : 冒烟测试 ; 边界测试 ; 彻底 全覆盖类型的随机测试 性能测试 关于冒烟测试, 应该是微软首先提出来的一个概念 冒烟测试就是在每日 build 建立后, 对系统的基本功能进行简单的测试 这种测试强调功能的覆盖率, 而不对功能的正确性进行验证 至于冒烟测试这个名称的来历, 大概是从电路板测试得来的 因为当电路板做好以后, 首先会加电测试, 如果板子没有冒烟再进行其它测试, 否则就必须重新来过 冒烟测试的说法据说是 : 象生产汽车一样, 汽车生产出来以后, 首先发动汽车, 看汽车能否冒烟, 如果能, 证明汽车最起码可以开动了 说明完成了最基本的功能

第一道防线 : 冒烟测试 基本 给人很大信心 import static org.junit.assert.*; import org.junit.test; public class BinarySearchSmokeTest { @Test public void smoketestsforbinarysearch( ) { int[] arraywith42 = new int[] { 1, 4, 42, 55, 67, 87, 100, 245 ; assertequals(2, Util.binarySearch(arrayWith42, 42)); assertequals(-1, Util.binarySearch(arrayWith42, 43)); 一般将许多 ( 几千个 ) 冒烟测试组成一个测试组, 每一次构建时运行 将每一个测试足够小, 来保持其速度快 而在编写代码之前就编写冒烟测试, 就叫做 测试驱动开发 (TDD)

第二道防线 : 边界测试探测和验证代码在处理极端或偏门情况时会发生什么 首先测试数组长度边界 ( 空数组 长度为 1 的数组 非常大的数组 ) int[] testarray; @Test public void searchemptyarray( ) { testarray = new int[] {; assertequals(-1, Util.binarySearch(testArray, 42)); @Test public void searcharrayofsizeone( ) { testarray = new int[] { 42 ; assertequals(0, Util.binarySearch(testArray, 42)); assertequals(-1, Util.binarySearch(testArray, 43));

足够大的数组如何测试? 创建足够大的数组? 只需测试中间值的计算 不是溢出一个负数即可 具体思路 : 1. 无法使用足够大数组直接测试 ; 2. 可以编写足够测试, 确信 BinarySearch 在小数组上执行正确 ; 3. 当用到相当大的数值时, 可单独测试计算中间值的方法, 这与数组无关 ; 4. 于是只需保证 : 计算中间值没问题时,BinarySearch 刻正确实现 ; 并且计算中间值过程没有问题 将易于溢出的计算隔离出来单独测试, 编写函数 : static int calculatemidpoint(int low, int high) { return (low + high) >>> 1; 然后将代码中的 int mid = (low + high) >>> 1; 变成 int mid = calculatemidpoint(low, high); 然后不断测试 calculatemidpoint 来保证其正确进行

如此改动 : 1. 运用了内联的方法, 没有损失性能 ; 2. 提高了代码的可读性 ; 3. 提高了代码的可测试性 这段代码通过了测试, 则可以确定 位运算符 可以正确实现其功能! 这样构造出了新的测试代码 : @Test public void calculatemidpointwithboundaryvalues( ) { assertequals(0, calculatemidpoint (0, 1)); assertequals(1, calculatemidpoint (0, 2)); assertequals(1200000000, calculatemidpoint (1100000000, 1300000000)); assertequals(integer.max_value - 2, calculatemidpoint (Integer.MAX_VALUE-2, Integer.MAX_VALUE-1)); assertequals(integer.max_value - 1, calculatemidpoint (Integer.MAX_VALUE-1, Integer.MAX_VALUE));

其次, 测试关于目标元素位置的边值 : 数组中的第一项 最后一项和正当中一项 @Test public void testboundarycasesforitemlocation( ) { testarray = new int[] { -324, -3, -1, 0, 42, 99, 101 ; assertequals(0, Util.binarySearch(testArray, -324)); // first position assertequals(3, Util.binarySearch(testArray, 0)); // middle position assertequals(6, Util.binarySearch(testArray, 101)); // last position 运用一些 0 与负数, 最大和最小数值来测试 : public void testforminandmaxinteger( ) { testarray = new int[] { Integer.MIN_VALUE, -324, -3, -1, 0, 42, 99, 101, Integer.MAX_VALUE ; assertequals(0, Util.binarySearch(testArray, Integer.MIN_VALUE)); assertequals(8, Util.binarySearch(testArray, Integer.MAX_VALUE)); 上述所有的测试都用过了, 那么代码就一定能在任何场景下都正常工作吗? The words of Joshua Bloch echo in my mind: It is hard to write even the smallest piece of code correctly. 编写代码时, 程序员的测试总是忽略一些应用场景, 所以要创新必须跳出旧的思维模式 如此, 我们需要覆盖更广的测试, 而不仅是一些具体例子

第三道防线 : 随机测试上述具体测试的例子仅仅为所有可能输入的一个很小的子集, 为达到全面测试, 我们需要 : 1. 一种能产生大量具有不同特征的数据量的方法 ; 2. 一组能针对各种输入集合的断言 运用 java.util 包中随机数产生器和 Arrays 类中的方法, 解决第一个需要 public int[] generaterandomsortedarray(int maxarraysize, int maxvalue) { int arraysize = 1 + rand.nextint(maxarraysize); int[] randomarray = new int[arraysize]; for (int i = 0; i < arraysize; i++) { randomarray[i] = rand.nextint(maxvalue); Arrays.sort(randomArray);// 保证数组有序 return randomarray; 第二个需要, 指的是对于任何输入数组和查找目标值,assert 必须为真, 作者称之为 推理 推理测试越充分, 即其正确的时候越多, 我们对代码的正确性越有信心

推理 1: 如果 BinarySearch(testArray, int target) 返回 -1, 则 testarray 不含 target; 推理 2: 如果 BinarySearch(testArray, int target) 返回 n(>=0), 则 testarray 的第 n 个位置是 target 测试这两个推理 : public class BinarySearchTestTheories { Random rand; @Before public void initialize( ) { rand = new Random( ); @Test public void testtheories( ) { int maxarraysize = 1000; int maxvalue = 1000; int experiments = 1000; int[] testarray; int target; int returnvalue; while (experiments-- > 0) { testarray = generaterandomsortedarray(maxarraysize, maxvalue);

if (rand.nextboolean( )) { target = testarray[rand.nextint(testarray.length)]; else { target = rand.nextint( ); returnvalue = Util.binarySearch(testArray, target); asserttheory1(testarray, target, returnvalue); asserttheory2(testarray, target, returnvalue); public void asserttheory1(int[] testarray, int target, int returnvalue) { if (returnvalue == -1) assertfalse(arraycontainstarget(testarray, target)); public void asserttheory2(int[] testarray, int target, int returnvalue) { if (returnvalue >= 0) assertequals(target, testarray[returnvalue]); public boolean arraycontainstarget(int[] testarray, int target) { for (int i = 0; i < testarray.length; i++) if (testarray[i] == target) return true; return false; 先 1000 组测试, 再将 maxarraysize 设成 10000, 都通过了测试 可是这就完了吗?

这只测试了一半 如果 binarysearch 的返回值为有关某事为真, 那么 testarray 和目标有关的这件事为真, 可是 如果 testarray 和目标有关的这件事为真, 那么 binarysearch 的返回值为有关某事为真 对不对呢? 例如 : binarysearch 的返回值为 -1 时,target 不在数组中, 而我们不能肯定当 target 不再数组中时,binarysearch 一定返回 -1 同理, 当 : binarysearch 的返回值为 n 时, target 在数组中第 n 个位置, 而我们不能肯定当, target 在数组中第 n 个位置时,binaryseach 一定返回 n 这样, 我们需要 Jeff Offut 发明的 变异测试 (mutation text) 其基本思想是修改被测代码使之带有一些 bug, 如果测试依然正确通过, 则说明这些测试或源代码无法满足需要 下面我们将 binarysearch 将一定修改 : 如果 target 大于 424242 而且不在数组中, 返回 -42 而不是 -1

public static int binarysearch(int[] a, int target) { int low = 0; int high = a.length - 1; while (low <= high) { int mid = (low + high) / 2; int midval = a[mid]; if (midval < target) low = mid + 1; else if (midval > target) high = mid - 1; else return mid; if (target <= 424242) return -1; else return -42; 测试通过了, 说明了原来的测试并不全面!

增加推理 3 与推理 4 推理 3: 如果 testarray 不包含 target, 它就必须返回 -1 推理 4: 如果 textarray 在位置 n 上包含 target, 那么 binarysearch 必须返回 n 于是增加测试 : public void asserttheory3(int[] testarray, int target, int returnvalue) { if (!arraycontainstarget(testarray, target)) assertequals(-1, returnvalue); public void asserttheory4(int[] testarray, int target, int returnvalue) { assertequals(gettargetposition(testarray, target), returnvalue); public int gettargetposition(int[] testarray, int target) { for (int i = 0; i < testarray.length; i++) return -1; if (testarray[i] == target) return i; 这回测试中出现了错误! 多余的, 推理 4 中已包含推理 3 为防止代码重复 : arraycontainstarget(int[ ] testarray, int target) { return gettargetposition(testarray, target) >= 0;

用以下修改的程序来使 JUint 在测试失败时给出更多的文本信息 : public void asserttheory4(int[] testarray, int target, int returnvalue) { String testdatainfo = "Theory 4 - Array=" + printarray(testarray) + " target=" + target; assertequals(testdatainfo, gettargetposition(testarray, target), returnvalue); 再次运行测试程序, 出现以下结果 : java.lang.assertionerror: Theory 4 - Array=[2, 11, 36, 66, 104, 108, 108, 108, 122,155, 159, 161, 191] target=108 expected:<5> but was:<6> 我们马上知道错误出在了那里 : 我们没有把重复值考虑在内! 这就是前边所说的可以继续修改 binarysearch 的地方

Mistakes are the portals of discovery. 错误是发现之门 James Joyce 想法 : 1. 在 binarysearch 搜到 target 时, 记录下位置, 再接着在左边继续二分查找, 最后返回最小的 target 位置 2. 修改 gettargetposition 在参数中用两个 int* 记录 target 出现最早和最晚的位置, 只要 binarysearch 的返会值在此范围内即可

性能测试 经过我们的检验, 此程序很难再有任何 bug 了, 但是, 对于一个漂亮的程序, 不检验其速度是不能展现其漂亮特性的 所以, 我们增加性能测试, 来测试二分查找的速度 首先想到的是用系统时钟, 可是二分查找的速度实在太快了, 系统时钟没有那么精确的时钟 在其中插入一个计数器是个不错的选择

public static int binarysearchcomparisoncount(int[] a, int target) { int low = 0; int high = a.length - 1; int comparisoncount = 0; while (low <= high) { comparisoncount++; int mid = (low + high) >>> 1; int midval = a[mid]; if (midval < target) low = mid + 1; else if (midval > target) high = mid - 1; else return comparisoncount; return comparisoncount;

基于这个代码, 我们给出推理 5: 如果 testarray 的长度为 n, 那么 binarysearchcomparisoncount 必须返回一个小于或等于 1+log 2 n 的数 最后将这些推理添加到推理列表中 :... while (experiments-- > 0) { testarray = generaterandomsortedarray( ); if (rand.nextint( ) % 2 == 0) { target = testarray[rand.nextint(testarray.length)]; else { target = rand.nextint( ); returnvalue = Util.binarySearch(testArray, target); asserttheory1(testarray, target, returnvalue); asserttheory2(testarray, target, returnvalue); asserttheory3(testarray, target, returnvalue); asserttheory4(testarray, target, returnvalue); asserttheory5(testarray, target);... 设定不同的 maxarraysize 多跑几次, 作者设成 1,000,000 去吃午饭! 推理 5 好像 不成立???

结论 冒烟测试 ; 边界测试 ; 彻底 全覆盖类型的随机测试 性能测试 The only time you don t fail is the last time you try anything and it works. William Strong 这些测试完成后, 我们对这段二分查找代码有了很大的信心!!! 几乎可以断言代码的正确性

结论 这一章, 展示了测试的代码也可以相当漂亮, 测试代码的编写可以像原代码本身一样需要创造力 Some tests are beautiful for their simplicity and efficiency. Other tests are beautiful because, in the process of writing them, they help you improve the code they are meant to test in subtle but important ways. Finally, some tests are beautiful for their breadth and thoroughness. 向艺术家们学习 : 画家经常放下手中的画笔, 远离画布一段距离, 围它转一转, 翘着脑袋左右看看, 再在不同光线不同角度下看看 再寻求美的过程中, 编写代码也需要不时用挑剔的眼光从不同的角度审视代码, 这将使我们成为更优秀的程序员, 并写出更漂亮的代码!