当前位置:首页 > 嵌入式 > 嵌入式硬件
[导读]TDD有助于确保你的代码做你想要做的事。如果不是这样,如何才能建立一个可靠的系统呢?它帮助你让代码在最开始时保持正确,它建立一个逐步测试的组件,帮助你维持代码的运行。你在发现、追踪和修改bug上要花掉相当多的时间。很多开发人员现在都用TDD来防止这些bug的出现。它基本上改变了你的编程方式。

要点

1.为什么你会遇上这些bug?因为它们是你放的。

2.在TDD(测试驱动的开发)中,你会在一个严格的反馈循环中,开发测试与生产代码。

3.TDD可能有助于避免恼人的Zune bug。

4.目标硬件瓶颈有多种形式,你可以在严格的TDD反馈循环中,用TDD来避开瓶颈。

5.TDD帮助你确保自己的代码如期望那样运行。但如果不是这样,你该如何建立一个可靠的系统?

6.TDD快速地发现小的和大的逻辑错误,防止出现bug,使最终得到较少的bug。

我们的工作方式都是编写代码,然后努力让它运行起来。先建立,然后改错。测试是以后的事,即写完代码后才要做的事。在不可预期的调试工作上,大概要花掉我们一半的时间。在日程表上,调试工作都穿着测试与集成的外衣。它是风险与不确定性的一个来源。修正了一个bug可能会产生另一个bug,有时甚至是一连串的bug。

保持调试的统计有助于预测要花多少时间才能消除bug。你要度量和管理bug。看曲线的拐点,拐点表示了趋势,告诉你最后修正的bug要比产生的多。拐点表示的是已经做的事,但你永远不知道是否在代码的某个阴暗角落还躲藏着其它的致命bug。

可制造性设计的一个方面是确定为什么你会有这些bug。答案很简单:错误是我们放进去的。这就是我们的工作方式。在开发以后的测试时,就会发现问题(图1和参考文献1)。我们在开发时会制造错误,测试的工作就是找到这些问题。只要仔细地测试,就会发现错误。开发后的测试工作意味着必须找到、修复和管理大量的错误。

图1,在开发以后做测试时,会发现缺陷

这种调试居后的编程程序是当今最常见的编程方式。先写代码,再调试它。调试居后的编程方式有风险。人都会犯错误。你既不能确定bug将在何时现身,也不能确定会花多长时间才能发现它们(图2)。

图2,人都会犯错误。你无法确定bug何时出现,以及要花多少时间才能找到它们

当发现一个bug的时间(TD)增加时,寻找bug根源的时间(TFIND)也会增加,通常增加得更多。如果从错误的引入到发现要花数小时、数天、数周,甚至数月时间,你已忘掉了当时的背景,必须开始做bug大扫荡。当你在开发周期以外发现缺陷时,就必须管理bug。对于有些bug,发现的时间不会影响修复的时间(TFIX),但有些代码的运行也可能依赖于bug,修改这些bug会造成其它bug。

短周期以及主动的测试自动化可节省时间和工作量。这时,你再不需要重复繁重而易错的手工测试。有了测试自动化,重复测试几乎不会增加额外工作量。测试自动化快速地探测出副作用,避免了对调试事务的需求。

另一种方案是TDD(测试驱动的开发),它在一个严格反馈的循环中开发出测试代码与生产代码(参考文献2和3)。一个TDD微循环是:编写一个测试,未编译时观察该测试,做编译且测试失败,使编译通过,清除任何多余内容,并重复该过程直至结束。编写测试代码与编写生产代码是整合的过程。如果犯了一个错误,没有通过新测试,你马上就可以知道并改正错误。测试会告诉你是否通过了新测试却产生了某个错误。在设备测试装置中加入自动化测试(图3),就可以自由地做重复测试。

图3,测试会告诉你是否通过了新的测试,但却引入了一个bug。自动测试要插入到一个单元测试装置中

在TDD反馈回路中做开发与测试时,只能避免一部分bug的出现,但不能完全消除。TDD对设计以及时间的分配方式有着意义深远的影响。

与后调试的编程模式相反,TDD并不包含追踪错误的风险与不确定性(图4)。当发现一个错误的时间接近于0时,寻找错误根源的时间也会趋于0。刚产生的代码问题通常显而易见。如果不那么明显,则开发人员只要简单地恢复刚做的修改,就可以回到一个可运行的系统。寻找和修改错误的时间和产生的时间一样少,只有当程序员记忆随时间而模糊,并且有更多的代码依赖于较早的错误时,事件才会变糟。

TDD为错误提供了即时的通知,可防止出现很多要被迫追踪的bug。TDD可防止出现缺陷,而后调试编程会带来耗时耗力的调试工作。

TDD可能有助于避免恼人的Zunebug。微软公司的Zune是为了与苹果公司的iPod竞争。2008年12月31日,Zune变成了“专为一天的程序块(abrick for a day)”。12月31日是新年前夜,是一个闰年的最后一天,这是30G Zune要经历的第一个闰年。很多人都将Zune错误归因于时钟驱动程序中的一个函数。虽然列表1中的代码并非实际的驱动程序码,但它有相同的效果。你可以从列表1中Zune的无限循环中找到一些端倪吗?

图4,TDD对于设计以及时间的使用有深远的影响。与调试居后的编程模式比较,TDD

没有回溯追踪bug的风险与不确定性


图5,对快速反馈的需求使TDD微循环离开目标硬件,而原生地运行在开发系统上。一个TDD循环包括双重目标的风险,但提供了快速TDD反馈回路的好处

很多代码阅读专家审查了这个代码,并得出了可能与您一样的错误结论。闫年的最后一天是该年第366天,而Zune对这种情况的处理是错误的。在这一天,该函数永远不会返回!我编写了设定年份以及年中天数的代码,看是否像90%的Zune bug专家预测的那样,将天数的布尔代码设定为等于或大于366就能解决问题。代码放入测试装置后,我编写了测试用例(列表2)。和Zune一样,测试进入了一个无限循环。我采用了经过数千名程序员审核的适当修复方法。出乎我的意料,测试失败了;设定年份与天数的测试认为日期是2009年1月0日。新年前夜,人们仍会拥有自己的音乐,但Zune仍有个bug。

一次测试就可以防止Zune bug。可你怎么知道要去写这样一个测试?只有知道bug在哪里才会写测试。问题是,你并不知道bug在哪里;它们可以在任何地方。所以,这意味着你必须为所有的部分写测试,至少是所有可能中断的地方。难以想象要考虑到所有需要测试的东西。但不必担心,你不需要针对全年每一天做测试。你只需要一个针对有关天数的测试。

计算机编程很复杂,TDD能够系统化地让你的代码按本意运行起来,并提供能使代码工作的自动化测试用例。

嵌入设计

当我首次使用TDD时,我认识到,它可能有助于解决一个问题:目标硬件的瓶颈,这是令很多嵌入软件开发人员头疼的事情。瓶颈有多种形式,你可以使用TDD,在严格的TDD反馈循环期间避免瓶颈的出现。很多嵌入开发工作都已实现了软硬件的并行开发。如果软件只能在目标硬件上运行,则可能浪费至少一次的时间。例如,目标硬件可能迟至交付期还不可用,推迟了软件的测试;硬件可能昂贵且稀少;或者它本身就有问题。目标硬件还可能有长的建立时间或长的上传时间。大多数嵌入开发团队都遇到过这些问题,它们会减缓进度,并减少了建立今天复杂系统的反馈。

为避免目标硬件的瓶颈,可以采用“双重目标”法,即设计自己的生产代码与测试,使之大部分运行在标准PC上。但双重目标有自己的风险。开发系统中测试代码的信任度是建立在交付给目标以前的代码上。大多数双重目标风险是源于开发环境与目标环境之间的差异。这些差异包括对语言特性支持的改变量、不同编译器的bug、运行时库的差异、文件名差异,以及不同的字长等。由于这些风险,你会发现,在一个环境下能无错运行的代码,可能在另一个环境下出现测试错误。

不过,执行环境中潜在的差异不应成为阻碍采用双重目标方法的理由。相反,你可以在实现目标的路途中解决这些障碍。嵌入TDD周期在不牺牲优点的前提下,克服了挑战。

开发循环

当建立与测试循环只需几秒时间时,TDD是最有效的。这种方案为大多数程序员排除了在循环中使用目标硬件的情况。快速反馈的需求将TDD微循环与目标分离开,而运行在开发系统上。图5显示了一个TDD循环,它包含着双重目标的风险,提供了快速TDD反馈循环的好处。

表1中所列的各个阶段,预计可以在相应的阶段发现问题。例如,你会发现每个阶段都有助于找到这些问题。第1阶段会在你编程时给出快速反馈,确定代码做你想要做的事。第2阶段确保你的代码是在两种环境下编译。第3阶段确保代码在主处理器和目标处理器上的运行相同。评估硬件可能需要比目标更多的存储器,这样才能把测试代码和生产代码都装入地址空间。有时候,如果你有一个可靠的目标硬件,它有空间运行对单元的测试,也可以省略掉第3阶段。第4阶段是在目标硬件上运行测试。在第4阶段可以引入一些依赖于硬件的单元测试。第5阶段是看你的系统完全整合时,是否如其应该的那样运行。至少让第5阶段的某些部分自动运行,这是一种好的想法。采用TDD的团队会发现第1阶段中的巨大价值,可能不要实现全部各个阶段。

嵌入TDD循环并不能阻止所有问题,不过它应有助于在适当的阶段发现大多数刚刚产生的问题。你还应至少每个夜晚手动执行第2至第4阶段。连续的集成服务器(如Cruise Control或Jenkins)都可以观察你的源码库,在check-in后开始做建立工作。

TDD有助于确保你的代码做你想要做的事。如果不是这样,如何才能建立一个可靠的系统呢?它帮助你让代码在最开始时保持正确,它建立一个逐步测试的组件,帮助你维持代码的运行。你在发现、追踪和修改bug上要花掉相当多的时间。很多开发人员现在都用TDD来防止这些bug的出现。它基本上改变了你的编程方式。

TDD能快速地发现小的和大的逻辑错误,阻止bug的产生,并最终得到较少的bug。较少的bug也意味着较少的调试时间,以及较少的缺陷。当新代码危及一个约束或一个假设时,测试会告诉你。然后,有良好结构的测试会成为一种形式的可执行文档。

TDD还让你放心,这种信心来自于一个带有完备回归测试组件的彻底测试代码。采用TDD的开发人员称周末不再受干扰,并且睡眠更好。TDD还监控进度,追踪当前的工作,以及做了多少工作。当代码变得难以测试时,它还对设计问题提出早期警告。

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

史胜辉,在MTK工作了11年,一直在基带芯片的USB驱动领域做开发和验证。从最开始做USB2.0/3.0 IP验证和驱动开发到后面带领团队做上层协议驱动开发,以及跟硬件设计部门合作开发全新的USB硬件加速器。

关键字: 基带芯片 驱动领域 驱动开发

回想七年前,我的第一家公司……

关键字: bug 代码 编程 程序员

点击上方名片关注我们朱老师推荐语:此岗位为AIoT终身成长大会员同学提供的自己公司的岗位内推,总部在深圳,是一家专业从事闭路电视监控设备、会议摄像机的研发、制造、销售的高科技企业,有学过嵌入式课程或者海思项目的同学,想换...

关键字: 开发工程师 linux驱动 驱动开发

最近在学习MIPI接口的LCD驱动开发与调试,这里我主要用的是MIPI-DSI接口,它学习起来真的是太复杂了,特别是对于我这种很久都没写驱动来说更是头疼,但是头疼归头疼,工作咱们还是要完成的,那就只能硬着头皮往下肝吧!首...

关键字: MIPI 驱动开发 调试

关注、星标嵌入式云IOT技术圈,精彩及时送达来源|  Linux与SoC整理出了6种驱动开发时与设备注册、设备树相关的调试方法,彼此间没有优先级之分,每种方法不一定是最优解,但可以作为一种debug查找问题的手段,快速定...

关键字: 驱动开发

最近在学习MIPI接口的LCD驱动开发与调试,这里我主要用的是MIPI-DSI接口,它学习起来真的是太复杂了,特别是对于我这种很久都没写驱动来说更是头疼,但是头疼归头疼,工作咱们还是要完成的,那就只能硬着头皮往下肝吧!首...

关键字: MIPI 驱动开发 调试

哈喽,我是老吴,我来继续分享我的学习心得啦。gpio和pinctrl子系统在内核里的使用率非常高,和嵌入式产品的关联非常大。从这两个子系统开始学习驱动开发是个不错的入门选择。本文目录:一、gpio 与 pinctrl二、...

关键字: gp pi 驱动开发

安全研究员Carl Schou发现,在加入一个名为“%p%s%s%s%s%n”的 Wi-Fi 网络后,他的iPhone的Wi-Fi功能被“禁用”。

关键字: 苹果 iOS bug

最近,一则视频被广泛转载,疑似“刹车失灵”系软件bug。

关键字: 特斯拉 bug

最近某程序员发了一个令自己奇怪的事儿:说自己已加班一年有余,工作从不敢出错,怕出现bug失误,平时和领导沟通的也不多,绩效稳定在3.5。

关键字: 程序员 bug
关闭
关闭