《测试驱动的嵌入式C语言开发》——3.1节具有可测性的C模块的那些元素

3.1 具有可测性的C模块的那些元素
本书中的示例将采用模块这个概念。在我们的目的之下,模块就是系统中一个完备的部分,它有明确定义的接口。这个定义并没有讲一个模块有多大。在本书中,我们只会用很小的模块。这些示例中的模块会恰好与编译单元相同,然而在现实世界中,并不是所有的模块都与某个单一的编译单元相对应。你会发现可测试的代码需要模块化。你还会发现TDD会自然而然地产生模块化设计。
可测试性对于测试有相当大的正面影响。为了创建模块化的C程序,我们需要抽象数据类型这个概念。Barbara Liskov在她所著的《Programming with Abstract Data Types》[Lis74]一书中这样定义抽象数据类型(Abstract Data Type,ADT):“抽象数据类型只用可能对它进行的那些操作来间接定义,并且这些操作的效果(也可能是代价)有数学上的约束。”
在抽象数据类型(ADT)中,一个模块的数据被当做私有成员来对待。它被封装起来。我们可以使用几种模块化方法来封装模块的数据。第一个选择是用静态的只有在.c文件中可见的变量,这样只有同一编译单元中的函数才能访问它们。数据只能间接地通过在.h文件中有原型定义的那些模块的公共接口来访问。这个方式适用于只有一套数据需要处理的模块,有时称为单一实例模块(single-instance module)。
当一个模块要为不同的客户管理不同的数据时,可以使用多实例模块(multiple-instance module)。在多实例模块中,必须要初始化数据结构并把它传回给客户以保持其上下文。这里就是抽象数据类型发挥作用的地方。可以用typedef在头文件中提前声明结构体,像这样:

只要没有代码去解引用它,编译器会很乐意让我们把定义不完全的指针传来传去。可以在实现CircularBuffer的.c文件中定义结构体的成员,这样就有效地隐藏了数据,从而使得只有那些以该数据结构的完备性为责任的模块才能操作它。如果你熟悉POSIX接口(Portable Operating System Interface of UNIX)中的pthread库,就会明白它使用的就是这种技术。Unix中的FILE(文件)是另一个抽象数据类型的例子。
当用TDD来创建C模块时,我们会用到以下文件及惯例:
用头文件来定义模块的接口。对于单一实例模块,头文件由函数原型构成。对于抽象数据类型,除了函数原型,还会有一个用typedef来指向提前声明的数据结构的指针。正如多次提到的,隐藏数据结构是为了隐藏模块的数据细节。
源文件用来包含对接口的实现。它同时会包含任何所需的私有辅助函数和隐藏的数据。模块的实现会管理模块数据的完备性。对于抽象数据类型,提前声明的数据结构成员会在源文件中定义。
测试文件用来包含测试用例,以保持测试代码和产品代码分离。每个模块都至少有一个测试文件,一个测试文件通常仅包含一个,但有时会是几个测试组。测试组围绕组中所有测试通用的数据来组织。当一些测试需求的建立与其他测试显著不同时,我们就需要有多个测试组,甚至多个测试文件。
模块初始化及清理函数。每个管理着隐藏数据的模块都应该有初始化及清理函数。抽象数据类型完全隐藏的内部结构必然需要它们。C++把这个想法内置到构建和析构函数中。按照惯例,本书会为每个模块建立Create(创建)和Destroy(销毁)函数。对于由独立函数组成的模块,如strlen()和sprintf()这样的没有内部状态的模块将不会需要初始化与清理。
遵循这些实践及习惯,代码将变得更容易测试并容易阅读和扩展。并不是完全不能测试那些可以随意访问数据结构/函数的代码,只是那会更难一点。在第一个例子中,本书将会用单一实例模块来测试驱动开发一个LED(发光二极管)驱动程序。我们以后再使用抽象数据类型。

时间: 2025-01-21 06:11:40

《测试驱动的嵌入式C语言开发》——3.1节具有可测性的C模块的那些元素的相关文章

《测试驱动的嵌入式C语言开发》——导读

目 录 第1章 测试驱动开发1.1 为什么我们需要TDD1.2 什么是测试驱动开发1.3 TDD的机理1.4 TDD的微循环1.5 TDD的好处1.6 对于嵌入式开发的益处第一部分 开 始第2章 测试驱动开发的工具和约定2.1 什么是自动化单元测试框架2.2 Unity:一个全部用C实现的自动化测试框架2.3 CppUTest:一个用C++实现的自动化单元测试框架2.4 单元测试也会崩溃2.5 "四阶段"模式2.6 我们到哪里了第3章 开始一个C语言模块3.1 具有可测性的C模块的那些

《测试驱动的嵌入式C语言开发》——第1章测试驱动开发

第1章 测试驱动开发我们都做过这样的事--写一大堆代码然后艰难地使它工作起来.也就是先建造再修正.测试是在代码写完之后的事情.测试总是一件后面加上来的事情,这也是我们过去唯一所知的方法.这种很难预料的过程被亲切地称为"调试"(debugging),我们可能会在其中花掉半个小时.调试的过程在我们的进度中被"测试"和"集成"粉饰起来.它总是风险和不确定的来源.修改一个bug可能导致产生另一个,有时是一系列其他的bug.我们往往会统计这些数据来预测把b

《测试驱动的嵌入式C语言开发》——第3章开始一个C语言模块

第3章 开始一个C语言模块在本章里,我会带你浏览用测试驱动来开发一个新的C模块首先要经历的那些步骤.在第4章里,我们则会全速前进来完成这个模块.从这一章开始并且贯穿本书,我们会关注到底能不能实现Dijkstra的不引入bug的愿景.我们所用的工具就是TDD.

《测试驱动的嵌入式C语言开发》——3.5节先测试驱动接口再测试驱动内部实现

3.5 先测试驱动接口再测试驱动内部实现好的接口对于设计良好的模块来讲很关键.前面几个测试会驱动接口设计.关注于接口意味着我们是从外向内开发代码的.测试作为接口的首个用户,从调用者(或客户端代码)的角度给出了开发代码的使用方式.从使用者的角度出发会产生可用性更强的接口.我通常也会让前面的几个测试来检验一些产品代码的边界条件.选择一个带边界检查的简单用例. 为了消除这个编译错误,在模块的接口声明头文件中增加这个接口函数原型: 写出并且通过这些测试能帮助我们实现以下目标:它定义了驱动程序的一个接口函

《测试驱动的嵌入式C语言开发》——3.3节写一个测试列表

3.3 写一个测试列表在开发新功能之前先创建一个测试列表会很有帮助.测试列表由需求衍生而来.测试列表定义了你对将需要完成的功能的最好的理解.这个列表不需要很完美.它只是个临时的文档,可能只记在一张卡片上或者笔记本上.你也可以直接把它当做注释输入到测试文件中.随着每个测试的添加,对应的注释将被删除.不要在写作这个列表上花太多时间,对于LED驱动程序来讲,花几分钟即可.我的初始测试列表如图3-1所示.在我们开始之前,先列出我们都需要测试什么. 请当心在创建测试列表时的报酬递减(diminishing

《测试驱动的嵌入式C语言开发》——2.6节我们到哪里了

2.6 我们到哪里了到这里,你应当对Unity和CppUTest的概况有了很好的了解,并且明白了如何用测试夹具和测试用例来定义测试.到目前为止你还没有看到测试驱动开发(TDD),为sprintf()写的那些测试并不是TDD,因为sprintf()是已经存在的代码.请你把新学到的知识运用到接下来的练习中.在接下来的两章中我们会用测试驱动来开发一些新代码.学以致用 在开发平台上建立一个测试环境.你可以从附录A中得到一些帮助.下载书中的代码,并运行makefile.可以访问本书的主页找到书中的代码:

《测试驱动的嵌入式C语言开发》——3.10节学以致用

学以致用 重新编写一个你自己的LedDriver,以便在第4章中使用.你可以在code/SandBox中找到开始所需的文件.其中有一个README.txt文件可以参考. 为一个内容为整型的先入先出的环状缓冲CircularBuffer写一个测试列表. 开始测试驱动开发CircularBuffer.选择检查初始状态并使用其接口的那些测试来做TDD.选择那些可以只用硬编码返回值就能通过的测试.你会需要修改makefile让它能够找到CircularBuffer的文件.在第4章之后你将会有机会完整地实

《测试驱动的嵌入式C语言开发》——3.7节测试驱动开发者的状态机

3.7 测试驱动开发者的状态机可以把TDD想象成在一个状态机(state machine)里工作,就如图3-2所示.在这一过程的每一步中,你只需关注解决一个具体的问题.首先,你要决定下一个增量的行为是什么并用测试表达你所期望的产出.然后你要满足编译的要求,也就是设计接口并让头文件和测试之间达成一致(有时你所选择的名字已经在其他地方用过了,你会在这一步中发现这类问题). 在接口和测试之间达成一致后,预期会得到一个链接错误,之后就要增加一个骨架实现,但并不期望它是正确的实现.测试失败是一个好的信号,

《测试驱动的嵌入式C语言开发》——2.3节CppUTest:一个用C++实现的自动化单元测试框架

2.3 CppUTest:一个用C++实现的自动化单元测试框架现在你已经见过了Unity,接下来我会快速介绍一下CppUTest,同时也是我更倾向于使用的对C和C++代码进行单元测试的自动化测试框架.事实上,不仅因为它是一个功能全面的测试框架,同时也因为我是CppUTest的作者之一.本书开始的几个例子会用Unity,在第8章之后会使用CppUTest.CppUTest是为了支持在多种操作系统上开发嵌入式软件而特别设计的.CppUTest的宏被设计成不需要了解C++也可以写测试用例.这使得C程序