用测试驱动开发的思路重构ADC LINUX驱动(一)
前言
测试驱动开发(TDD)是上个世纪末开始流行的一种敏捷开发模式。在大型的互联网应用或者知名IT公司中有不少拥趸,而本人在开发嵌入式代码的时候,从来没有实践或者使用TDD。从本人躺过的无数个坑里面,深感如果单元测试覆盖率高,或者用TDD的方式开发,或许会少很多低级的、逻辑上的、甚至是很多高级的错误。
国外有一本关于嵌入式TDD的教材–James W. Grenning 《Test-Driver Development for Embedded C》,中译本为《测试驱动的嵌入式C语言开发》,由机械工业出版社出版。读了本书后,还是有些失望,里面介绍的一些方法或许更适合在单片机或者ucos这样的微内核里适用,在嵌入式linux驱动开发里,不太合适。
本着试试的心态,用书中的一些方法结合linux的特点来尝试一下,在linux环境下的TDD或许有别样的效果和乐趣。我已经迫不及待的想尝试下。。。
可以参考原生sdk中的驱动:
AST2500片内ADC驱动详解
本文的所有源代码都可以在如下链接上下载:
https://github.com/WangKaiwh/linux_CPP_C_codes/tree/master/linux_driver/ADC_TDD
https://github.com/WangKaiwh/linux_CPP_C_codes/tree/master/linux_driver/test_template
开发环境
编译和运行环境:ubuntu10.04。用的比较老的版本,主要是为了更接近嵌入式的环境,嵌入式所用的linux内核版本一般不高。
定义通用测试框架–Unity的简化版
test_template.h
#ifndef __C_TEST_TEMPLATE_H__
#define __C_TEST_TEMPLATE_H__
#ifdef __cplusplus
extern "C"
#endif
#ifndef false
#define false 0
#endif
#ifndef true
#define true 1
#endif
#if __KERNEL__
// ... ##__VA_ARGS__, windows and linux compatible
#define OUTPUT_MSG(fmt, ...) do {\
printk(KERN_EMERG fmt, ##__VA_ARGS__);\
} while (0)
#else
// ... ##__VA_ARGS__, windows and linux compatible
#define OUTPUT_MSG(fmt, ...) do {\
printf(fmt, ##__VA_ARGS__); \
} while (0)
#endif
static inline int __ASSERT_EQUAL_INT(
const char *file,
const char *func,
int line,
int expected,
int actual)
{
if (expected != actual)
{
OUTPUT_MSG("%s, %s, %d, FAILED! expected: %d, actual: %d\n",
file, func, line, expected, actual);
return false;
}
OUTPUT_MSG("%s, %s, %d, OK\n", file, func, line);
return true;
}
#define TEST_ASSERT_EQUAL_INT(expected, actual) do{\
if (false == __ASSERT_EQUAL_INT(__FILE__, __func__, __LINE__, expected, actual))\
return -1;\
} while (0)
#endif
James的书中提到用Unity这个测试框架来进行TDD,在linux里面移植较为麻烦。暂且自己现实了一个简化版本–test_template.h,功能远不及Unity,先凑合着用。
关于Unity工具的使用和下载,参见如下链接:
http://www.throwtheswitch.org/unity
增加和搭建代码框架
新建adc_drv.c,复制sdk中的头文件和Makefile到本地,链接或者复制test_template到本地目录。
修改Makefile,使之可以正常编译。
环境搭建好后,主体代码如下:
#define ENABLE_TDD 1
// product codes
// tdd codes
#if ENABLE_TDD > 0
#include "test_template/test_template.h"
#endif
int __init adc_mod_init(void)
{
return 0;
}
void __exit adc_mod_cleanup (void)
{
}
MODULE_LICENSE ("GPL");
MODULE_AUTHOR("WangKai -- https://blog.csdn.net/kao2406");
MODULE_DESCRIPTION("ADC driver");
module_init (adc_mod_init);
module_exit (adc_mod_cleanup);
仅仅定义了模块的入口出口。下面开始正式TDD了。
TDD STEP1列功能清单
TDD最重要的一个步骤是列功能清单,站在使用者(API调用者)的角度去思考,产品代码有哪些功能需要完成。只有功能清单足够全面、覆盖率足够高,TDD的效果才会更好。这类似于一个夹具,当夹具足够精细时候,它所能抓住的东西更多,更少的遗漏一些细小的琐碎的BUG。
如下是我列出的本ADC驱动的功能清单:
1 模块加载后,所有的ADC通道0~15,都是默认disable的
2 模块加载后,默认的时钟频率分频0x40
3 模块加载后,注册字符设备
4 模块加载后,提供ioctl给用户态调用。
5 模块加载后,ioctl提供的功能有,设置ADC的时钟,测量ADC,开启ADC的通道
6 正常运行的驱动,用户enable某个通道后,才能正常测试ADC
7 正常运行的驱动,没有enable的某个通道,不能正常测试ADC
8 正常运行的驱动,用户是可以改变任意通道频率的,哪怕是disable的通道
9 正常运行的驱动,已经enable的某个通道,在关闭后,不能正常测试,此功能和7不同,功能9是动态的
10 驱动模块卸载后,字符设备消失
11 驱动模块卸载后,ADC驱动的所有的功能都不能用