2018-06-04 22:25:14 a13698709128 阅读数 142
  • I2C总线和触摸屏驱动移植实战-linux驱动开发第9部分

    本课程是linux驱动开发的第9个课程,主要内容是linux的I2C子系统以及电容触摸屏驱动的移植。学习本课程的核心点在于I2C子系统,目标是对I2C驱动框架的彻底理解和熟练运用。本课程承袭前面platform平台总线的讲解思路和基础,真正做到了对I2C总线做透彻清晰的讲解。

    9810 人正在学习 去看看 朱有鹏

         本博客主要介绍的是在Samsung 4412平台进行i2C驱动开发。驱动开发最后都需要在文件系统中注册设备节点,我将i2c设备以字符设备的方式进行注册,当然你也可以注册为混杂设备。混杂设备驱动开发可以算是字符设备开发的一种,但是相对简单一点,不能体现出整个字符设备开发的的整体框架,因为我也是处在学习阶段,所以就将该设备注册为 字符设备,文中我会详细的介绍i2c驱动的整个架构,但不会详细介绍怎么注册字符驱动,好的,下面开始:

       i2c_adapter可以理解做一个i2c的主控制器,里面封装了i2c的所有信息,包括时序,地址等。该适配器由i2c的主机创建,使用i2c_client和i2c_driver相连,从而使内核通相应的方法与设备进行交互。

      i2c_algorithm描述一个i2c主机的发送时序的信息,该类的对象algo是i2c_adapter的一个域,其中的master_xfer()注册的函数最终被设备驱动端的i2c_transfer()回调

     i2c_client描述一个挂接在硬件i2c总线上的设备的设备信息,也就是你对应去要驱动的芯片,即i2c设备的设备对象,与i2c_driver对象匹配成功后通过detected和i2c_driver以及i2c_adapter相连,在控制器驱动与控制器设备匹配成功后被控制器驱动通过i2c_new_device()创建。

     i2c_driver描述一个挂接在硬件i2c总线上的设备的驱动方法,即i2c设备的驱动对象,通过i2c_bus_type和设备信息i2c_client匹配,匹配成功后通过clients和i2c_client对象以及i2c_adapter对象相连。

    i2c_msg描述一个在设备端和主机端之间进行流动的数据, 在设备驱动中打包并通过i2c_transfer()发送。

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/platform_device.h>
#ifdef CONFIG_HAS_EARLYSUSPEND
#include <linux/earlysuspend.h>
#endif
#include <linux/regulator/consumer.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/cdev.h>
#include <asm/uaccess.h> 
#define DEV_MAJOR 0  ///主设备号 次设备号
#define DEV_MINOR 0


#define DEVICE_NAME  "ADC_DAC"
#define DEVICE_MINOR_NUM  2




#define PCF8591T_ADDRESS  0x48     ////定义i2c从机地址
#define PCF8591T_ADC      0x40     // 读寄存器 滑动变阻器


int numdev_major=DEV_MAJOR;//主设备号
int numdev_minor=DEV_MINOR;//次设备号


dev_t dev_no;//用来申请设备号
struct class *clas;//注册设备类


struct PCF8591T_pri{
struct cdev dev;
struct i2c_client *client;

};

struct PCF8591T_pri dev;


static void PCF8591T_write_byte(struct i2c_client *client,const unsigned char reg,const unsigned char val)
{
char txbuf[2]={reg,val};

struct i2c_msg msg[1]=
{
[0]={

.addr=client->addr,
.flags=0,                   //写标志
.len =sizeof(txbuf),
.buf=txbuf,
},

};

i2c_transfer(client->adapter,msg,ARRAY_SIZE(msg));  //


static char  PCF8591T_read_byte(struct i2c_client *client,const unsigned char reg)
{
int  ret;
char txbuf[1]={reg};       //要写的寄存器
char rxbuf[1]={0};         //收到的数据
struct i2c_msg msg[2]=
{
[0]={

.addr=client->addr,
.flags=0,                   //写标志
.len =sizeof(txbuf),
.buf=txbuf,
},
[1]={

.addr=client->addr,
.flags=1,                   //读标志
.len =sizeof(rxbuf),          //接收到的数据
.buf=rxbuf,
},

};

ret=i2c_transfer(client->adapter,msg,ARRAY_SIZE(msg));  //判断数据传输是否完成
if(ret<0)
{
pr_err("read reg error!\n");

}
ret=rxbuf[0];
return  ret;



static const struct i2c_device_id i2c_PCF8591T_id[]=
{
   {"PCF8591T",0},
   {}
     
};
static int dev_open(struct inode *ip, struct file *fp)
{
printk("==%s:\n",__FUNCTION__); 
    return 0;
}
static int dev_release(struct inode *ip, struct file *fp)
{
    printk("==%s:\n",__FUNCTION__); 
    return 0;
}
static long dev_ioctl(struct file *fp, unsigned int cmd, unsigned long arg)
{
unsigned char adc_data;
unsigned char get_data;
 ///copy_from_user(&reg_data,buffer,1);  ////内核空间地址指针  用户空间地址指针  数据长度
int ret;
ret=PCF8591T_read_byte(dev.client,PCF8591T_ADC);      //////返回的数据
if(ret<0)
{
pr_err("read reg error!\n");
 
}
adc_data=ret;
ret = copy_to_user(&adc_data,&get_data,1);   ////内核空间地址指针  用户空间地址指针  数据长度


   return 0;
}
struct file_operations fops ={
.open=dev_open,
.release =dev_release,
.unlocked_ioctl= dev_ioctl,

};


static int i2c_PCF8591T_probe(struct i2c_client *client,const struct i2c_device_id *id)
{
int ret;
    printk("==%s:\n",__FUNCTION__);     //模块加载的时候打印函数名
    
    dev.client=client; 
   
   
ret=alloc_chrdev_region(&dev_no,numdev_minor,DEVICE_MINOR_NUM,DEVICE_NAME);
    numdev_major = MAJOR(dev_no);

numdev_minor=MINOR(dev_no);
printk(KERN_EMERG "adev_region numdev_major %d !\n",numdev_major); //注册主设备号
printk(KERN_EMERG "adev_region numdev_minor %d !\n",numdev_minor); //注册从设备号

if(ret<0){    ///注册失败
printk(KERN_EMERG "register_chrdev_region req %d is failed!\n",numdev_major);
}

cdev_init(&dev.dev,&fops);     ///device 初始化
ret=cdev_add(&dev.dev,dev_no,1);  //添加设备到内核
    if(ret<0){    ///注册失败
printk(KERN_EMERG "cdev_add %d is fail! ");
}
    printk(KERN_INFO"match OK!\r\n");
   
    clas = class_create(THIS_MODULE,DEVICE_NAME);         //创建设备类
    device_create(clas,NULL,dev_no,NULL,"%s%d",DEVICE_NAME,1);
  return 0;

}

static int __devexit i2c_PCF8591T_remove(struct i2c_client *client)
{
     i2c_set_clientdata(client,NULL);                                  ///模块退出的时候打印函数名
     printk("==%s:\n",__FUNCTION__);
    
 
cdev_del(&dev.dev);
/*摧毁设备节点函数d*/
device_destroy(clas,MKDEV(numdev_major,numdev_minor));
class_destroy(clas);
return 0;
}

static struct i2c_driver i2c_PCF8591T_driver ={ // driver_regiester


   .probe    =i2c_PCF8591T_probe,
   .remove   =__devexit_p(i2c_PCF8591T_remove),
   .id_table =i2c_PCF8591T_id,
   .driver={
             .name = "PCF8591T",
             .owner=THIS_MODULE,
           },


};
static int __init i2c_PCF8591T_init(void)
{
   
   printk("==%s:\n",__FUNCTION__);
   return i2c_add_driver(&i2c_PCF8591T_driver);  //添加驱动
}

static void __exit i2c_PCF8591T_exit(void)
{
printk("==%s:\n", __FUNCTION__);
i2c_del_driver(&i2c_PCF8591T_driver);
}

late_initcall(i2c_PCF8591T_init);   //内核初始化列表 内核初始化分为 7个等级
module_exit(i2c_PCF8591T_exit);

MODULE_AUTHOR("fanxiangqiang");
MODULE_DESCRIPTION("PCF8591_I2c");
MODULE_LICENSE("GPL");

2017-04-05 14:50:38 daniel80110_1020 阅读数 8596
  • I2C总线和触摸屏驱动移植实战-linux驱动开发第9部分

    本课程是linux驱动开发的第9个课程,主要内容是linux的I2C子系统以及电容触摸屏驱动的移植。学习本课程的核心点在于I2C子系统,目标是对I2C驱动框架的彻底理解和熟练运用。本课程承袭前面platform平台总线的讲解思路和基础,真正做到了对I2C总线做透彻清晰的讲解。

    9810 人正在学习 去看看 朱有鹏

如何成为一名优秀的Android驱动程序员?(参考之前看过的一篇文章总结出来的,不记得原文链接了)要求如下:

一、Android驱动的基础知识

1.Android驱动是基于Linux驱动,强烈推荐阅读Linux Device Driver 3rd版,这本书讲了Linux下设备驱动的基础知识,要求反复细读。

2.能读懂和编写一些C程序。

3.能懂Java基础,因为Framework层的代码与驱动代码联系比较紧密,稍懂一些Java代码,会发现对整个驱动框架的了解更加熟悉。


二、Android/Linux相关驱动框架知识

1.需要Android/Linux相关的知识。

2.需要对Android各模块驱动框架的了解。

3.需要基本的Android调试能力。


三、相关的硬件知识和通信知识

1.Android驱动平时的工作就是调试各种外围设备,是直接跟硬件打交道,需要看得懂电路原理图,了解基本的显示原理和基本的摄像头成像原理等。

2.做Android手机,需要了解基本的通信相关知识,射频原理和基本的Modem相关知识,只有懂相关的硬件知识和通讯设备相关的基础知识,才可以写出更好的Android驱动程序。


四、热爱驱动开发和不断学习

    做Android驱动开发需要的是不断的学习,时刻保持着一股激情,不断的学习才能更好的完成日常的驱动开发任务,并能保持对开发的敏锐感觉。就如乔布斯所说的:Stay hungry, Stay foolish.

2019-04-24 19:30:29 a568713197 阅读数 917
  • I2C总线和触摸屏驱动移植实战-linux驱动开发第9部分

    本课程是linux驱动开发的第9个课程,主要内容是linux的I2C子系统以及电容触摸屏驱动的移植。学习本课程的核心点在于I2C子系统,目标是对I2C驱动框架的彻底理解和熟练运用。本课程承袭前面platform平台总线的讲解思路和基础,真正做到了对I2C总线做透彻清晰的讲解。

    9810 人正在学习 去看看 朱有鹏

前言

开始学习Linux驱动开发了,对于所有的程序员来说,任何语言,任何工具,第一个程序当然是Hello World了

最小驱动模块示意图

在这里插入图片描述

Hello World源码及分析

#include <linux/init.h>
#include <linux/module.h>

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("GYY");

static int hello_init(void)
{
        printk(KERN_EMERG "HELLO WORLD enter!\n");
        return 0;
}

static void hello_exit(void)
{
        printk(KERN_EMERG "HELLO WORLD exit!\n");

}

module_init(hello_init);
module_exit(hello_exit);

<linux/module.h>
MODULE_LICENSE 声明协议 GPL 开放源代码(如果不声明GPL 协议模块将无法在Linux中使用)Dual BSD
MODULE_AUTHOR 模块作者
<linux/init.h>
包含初始化宏定义的头文件
入口函数module_init(x)
出口函数module_exit(x)
打印信息printk是分级的
1、#define KERN_EMERG 0
紧急事件信息,系统崩溃之前提示,表示系统不可用
2、#define KERN_ALERT 1
报告信息,表示必须立即采取措施
3、#define KERN_CRIT 2
临界条件,通常涉及严重的硬件或软件操作失败
4、#define KERN_ERR 3
错误条件,驱动程序常用KERN_ERR来报告硬件的错误
5、#define KERN_WARNING 4
警告条件,对可能出现的情况进行警告
6、#define KERN_NOTICE 5
正常但又重要的条件,用于提醒
7、#define KERN_INFO 6
提示星系,如驱动程序启东时,打印硬件信息
8、#define KERN_DEBUG 7
调试级别的消息

使用Makefile进行编译

Makefile源码及分析

#使用bash语言
#!/bin/bash
#通知编译器我们要编译模块的那些源码
obj-m += mini_linux_module.o
#源码目录变量
KDIR := /home/topeet/work/iTop4412_Kernel_3.0
#当前目录变量
PWD ?= $(shell pwd)
#make命名默认寻找第一个目标
#make -C指调用执行的路径
#$(KDIR)Linux源码目录,即/home/topeet/work/iTop4412_Kernel_3.0
#M表示模块
#$(PWD)当前目录变量
#modules 要执行的操作
all:
	make -C $(KDIR) M=$(PWD) modules
#清除中间文件(以.o为后缀的文件,也可以清除其它的,只需要.ko为后缀的文件即可)
clean:
	rm-rf *.o

编译流程分析

在这里插入图片描述

编译

make

在这里插入图片描述

在开发板进行模块的操作

将.ko文件复制到开发板

cp mini_linux_module.ko /home/topeet/minLinux/system/drivers/hello_world/

安装模块

insmod .ko文件

在这里插入图片描述

查看模块

lsmod 

在这里插入图片描述

cat /proc/modules 

在这里插入图片描述

卸载模块

rmmod 模块名

在这里插入图片描述
问题** rmmod: can’t change directory to ‘/lib/modules’: No such file or directory **
解决方法:
1.mkdir /lib/modules 再次rmmod
2.提示rmmod: can’t change directory to ‘3.0.15’: No such file or directory
3.mkdir /lib/modules/3.0.15 再次rmmod
4.成功

2014-05-06 16:28:52 qiaoxinwei 阅读数 1902
  • I2C总线和触摸屏驱动移植实战-linux驱动开发第9部分

    本课程是linux驱动开发的第9个课程,主要内容是linux的I2C子系统以及电容触摸屏驱动的移植。学习本课程的核心点在于I2C子系统,目标是对I2C驱动框架的彻底理解和熟练运用。本课程承袭前面platform平台总线的讲解思路和基础,真正做到了对I2C总线做透彻清晰的讲解。

    9810 人正在学习 去看看 朱有鹏
I 测试驱动开发入门

一 通向测试驱动开发之路

敏捷方法简介:
  • 个人与互动 重于 流程与工具
  • 可用的软件 重于 详尽的文件
  • 与客户合作 重于 合约协商
  • 响应变化 重于 遵循计划
敏捷注重交流沟通,应对变化。

著名的敏捷开发方法:
  • Scrum
  • 极限编程(XP)
  • 功能驱动开发
  • Clear Case
  • 自适应软件开发(Adaptive Software Development)
二 单元测试简介

单元测试就是针对一个工作单元设计的测试。一个工作单元是指对一个方法的一个要求。

单元测试的共同特征:
  • 与其他代码隔离
  • 与其他开发人员隔离
  • 有针对性
  • 可重复
  • 可预测
其它类型的测试:
  • 用户界面测试
  • 集成测试
  • 压力测试
  • 用户验收测试
单元测试框架:NUnit等

与模拟对象分离:虚拟、伪对象(Mock)、存根(Stub)和模拟(Shim)

模拟对象(Mock Object)用来在应用程序中代表那些其他组件,有时代表外部资源。利用这些对象测试代码时,不需要担心与外部资源进行交互的后果。

在TDD中使用模拟时,应当了解以下概念:
  • 依赖注入(Dependency Injection)—— 对类进行内部实例化时,不是静态创建该类所依赖的对象,而是向其提供这些对象的一些实例,这些实例符合该依赖项所需要的接口。
  • 为接口设计,而不是为实现设计—— 在将另一个类或资源用作依赖项时,应当关注的不是他如何执行自己的任务,而是关心其接口是什么。同理,在设计和构建服务的类时,应当使用接口来抽象API中的功能。
  • 尝试限制依赖项
  • 不要模拟私有方法—— 如果正在编写一个测试,而且代码依赖于另一个类,应当仅模拟公共方法(而且是仅模拟哪些直接使用的公共方法)。
  • 不要欺骗——不要通过模拟来走一些捷径。
推荐的模拟框架:Moq

三 重构速览

TDD项目的生命周期:红灯、绿灯、重构。也就是说:开始是一个未通过的测试,因为还没有实现能够通过这一测试的逻辑;然后实现此测试,使其得以通过;最后重构代码,对其加以改进,同时不会影响测试结果。

代码测试覆盖率度量中应当始终包含所有功能代码。这些功能部分几乎包含了除以下对象之外的所有内容:实体对象、数据传输对象,以及任何以包含数据为惟一目的的对象或结构。

OOP(Object Oriented Programming)的三条一般原则:封装、继承、多态。

面向对象设计的SOLID原则:
  • 单一职责原则(Single Responsibility Principal):每个方法或类应当有且仅有一个改变的理由。
  • 开放/封闭原则(Open/Close Principal):软件(方法或类等)应当开放扩充且关闭修改。
  • 里氏替换原则(Liskov Substitution Principal):用超类代替应用程序中使用的对象时,应当不会破坏应用程序。也通常被称为“契约式设计(design by contract)”
  • 接口分离原则(Interface Segregation Principal):不应当强制客户端依赖岂不使用的接口。应该专门针对客户端的需要,创建更小、更精细的接口。
  • 依赖倒置原则(Dependency Inversion Principal):代码应当依赖于抽象概念,而不是具体实现;这些抽象不应当依赖于细节;而细节应当依赖于抽象。
DRY原则(Don't Repeat Yourself):避免重复代码。

代码异味(Code Smell):
  • 重复代码和相似类
  • 大型类和大型方法
  • 不恰当的注释
  • 不恰当的命名
  • 特征依赖:如果一个类过多地使用另一个类的方法,那就说它有些“特征依赖(feature envy)”
  • If/Switch过多
  • Try/Catch过多
重构方法:
  • 析取类或接口:将一个类分割为更小、针对性更强的类,或者从中析取出一系列更精细的接口。
  • 析取方法
  • 重命名变量、字段、方法和类
  • 封装字段
  • 用多态替换条件
  • 允许类型推断
四 模拟外部资源

1. 依赖注入介绍

依赖注入框架:Ninject
Ninject需要类来存储其用于生成依赖项具体实例的规则,这些类被称为模块。
在被重写的Load方法中定义一些规则,用来创建相应的类。
如果类在构造函数形参中所需要的是Ninject可以提供的,Ninject会自动创建一个实例并作为形参传递给该类。
如果类在构造函数形参中所需要的是Ninject所不能提供的,Ninject允许为特定接口创建Provider类。

使用Ninject的一个例子:
public BussinessService()
{
    private ILoggingComponent _loggingComponent;
    private IDatabaseAccessComponent _dataAccessComponent;
    ...
    public BussinessService(ILoggingComponent, loggingComponent, IDatabaseAccessComponent  databaseAccessComponent)
    {
        _loggingComponent = loggingComponent;
        _dataAccessComponent = databaseAccessComponent;
        ...
    }
}

public class CoreModule:NinjectModule
{
    public override Load()
    {
        Bind<ILoggingDataSink>().To<LoggingDataSink>();
        Bind<ILoggingComponent>().To<LoggingComponent>();
        Bind<IDatabaseAccessComponent>().ToProvider(new DataAccessComponentProvider);
    }
}

public class DataAccessComponentProvider:Provider<IDataAccessComponent>
{
    protected override IDataAccessComponent CreateInstance(IContext context)
    {
        var databaseConnectionString = .....
        return new DatabaseAccessComponent(databaseConnectionString);
    }
}

Client端:

BussinessService actual;
var kernel = new StandardKernel(new CoreModule()); //StandardKernel comes from Ninject framework
actual = kernel.Get<BussinessService>();

2. 抽象数据访问层 - Repository模式
存储库模式要求所有的数据访问都封装在一个存储库对象中,业务域类将用他来执行所有的持久化工作。
实体类
public class Persion
{
    public int Id {get; set;}
    public string FirstName {get; set;}
    public string LastName {get; set;}
}

存储库类
public interface IPersonRepository
{
    Person GetPerson(int personId);
}

public class PersonRepository : IPersonRepository
{
    pulbic Person GetPerson(int personId)
    {
        ...
    }
}

业务域类
public interface IPersonService
{
    Person GetPerson(int personId);
}

public class PersonService : IPersonService
{
    private readonly IPersonRepository _personRepository;
    
    public PersonService(IPersonRepository  personRepository)
    {
        _personRepository = personRepository;
    }
    
    public Persone GetPerson(int personId)
    {
        return _personRepository.GetPerson(personId);
    }
}

Client端可以使用Mock进行单元测试:

var personRepositoryMock = new Mock<IPersonRepository>();
personRepositoryMock.Setup(pr => pr.GetPerson(1)).Returns(new Person{Id = 1, FirstName = "Bob", LastName = "Smith"});
var personService = new PersonService(personRepositoryMock.Object);

II 将基础知识变为行动

五 敏捷开发流程

一、定义项目
1. 开发项目综述——是对应用程序业务需要的高级描述。它描述了应用程序的整体目标,部分关键工作流,以及应用程序的主要用户角色和类型。用于提供业务需求的骨架结构。
2. 定义目标环境——不仅要考虑应用程序的部署环境,还要考虑将如何构建应用程序,并将针对它收集哪些类型的基础设施架构需求。例如是基于Web的应用程序,or在用户的桌面上运行,or在盈动平台上运行?该应用程序是否有多个层?希望在峰值时有多少并发用户?应用程序需要提供何种响应时间?应用程序是否需要扩展?
3. 选择应用程序所需的技术——确定应用程序平台(.NET, J2EE...),以及所使用的框架和工具。

二、定义用户情景
用户情景——表示应用程序的业务需求,定义了应用程序的规则和工作流,它应当描述业务用户与应用程序之间的预期交互。
1. 收集情景——收集创建这些情景所需的信息,然后将其与业务比对已验证它们。3种常见的技术(主动->被动):跟班、客户会谈、联合应用程序设计(JAD)会话。当开发团队完成了满足用户情景所必需的功能之后,应当立即可供用户测试。
2. 确定待办事项表——代办事项表列出了为完成该应用程序需要做的工作(用户情景被分解为一些可以分配给开发人员来完成的功能)。

三、敏捷开发过程
大多数敏捷过程都特别重视一条思想:在很短的迭代过程中完成易于管理和理解的小型工作单元;尽可能快速、频繁地向用户提交软件。
1. 估计——估计不确定性锥形:在项目开始时所做工作评估的不准确因素可能为4。这意味着完成一项功能所需要的实际工作可能是估计值的25%,也可能是估计值的400%。
一种更你符合逻辑的做法是定期对待办事项表中的功能进行重新估计。
2. 迭代——敏捷方法鼓励采用短时迭代。
  • “零次迭代”:设置迭代,是第一次迭代,通常是在收集了主要的非功能需求并定义了用户情景之后,准备结束需求收集阶段。在这一期间创建开发环境,设置将会用到的应用服务器,设置OA环境并配置连续迭代服务器(CI负责编译应用程序,并运行所有单元测试和集成测试)。
3. 团队内部交流——建立项目的有关信息,共团队参考,减少低效冗长的会议。
4. 第一次迭代——在开发人员选择了要开始处理的功能之后,下一步就是阅读用户情景,并与业务分析师、项目经理等业务专家会谈,对功能进行回顾,以确保自己能够从业务的角度理解功能或用户情景。
5. 迭代中的测试——自动化的测试,保证所有单元测试和集成测试能够通过。
6. 结束迭代——迭代结束后的周一或周二安排一场有业务人员参加的“展示——告知”会议,向客户展示在上一个迭代周期内完成的工作,发现缺陷和修改。缺陷总处于最高优先级。改变指的是应用程序业务需求因为某种原因不够准确,这些新的或要修改的用户情景需要验证,分解为功能,还必须预计以工作量,然后被添加到代办事项表中,安排在随后的迭代周期中完成。

六 实现第一个用户情景

在计划工作时,以合理方式安排各项功能的开发顺序:从应用程序的核心开始,向外层发展。在将用户情景分解为功能时,一定要让他们保持小型化、相互隔离、可以测试。
在创建单元测试时,采用一致的标准为类、方法和变量命名,为它们起的名字应当是有意义的。
TDD要求仅编写能使测试通过的最少量代码。
一定要从多个方向进行测试,要测试方法的边缘情景:输入哪些位于期望边界的参数、超出边界的取值,以及预期之外的取值和条件。
在拥有一套通过的测试之后,可以对代码进行重构,使它更优化、可读性更强、更易于维护,牢记SOLID原则。

七 集成测试

单元测试确保各个组件满足在功能和用户情景中列出的业务需求,重要的一点是让单元测试以及执行这些测试的代码与其他组件或外部资源隔离。
集成测试用来验证正在开发的各个应用程序组件和外部资源能够正确地协同工作。
端对端测试是一种特殊的集成测试,用来验证应用程序代码可以根据应用程序的用户情景来执行整个业务工作流。
早集成、常集成。


2017-01-12 22:41:51 linolzhang 阅读数 314
  • I2C总线和触摸屏驱动移植实战-linux驱动开发第9部分

    本课程是linux驱动开发的第9个课程,主要内容是linux的I2C子系统以及电容触摸屏驱动的移植。学习本课程的核心点在于I2C子系统,目标是对I2C驱动框架的彻底理解和熟练运用。本课程承袭前面platform平台总线的讲解思路和基础,真正做到了对I2C总线做透彻清晰的讲解。

    9810 人正在学习 去看看 朱有鹏

        测试驱动开发,英文全称 Test-Driven Development,简称TDD,是一种不同于传统软件开发流程的新型的开发方法。它要求在编写某个功能的代码之前先编写测试代码,然后只编写使测试通过的功能代码,通过测试来推动整个开发的进行。这有助于编写简洁可用和高质量的代码,并加速开发过程。

        测试驱动开发的基本过程如下:

1. 快速新增一个测试;

2. 运行所有的测试(有时候只需要运行一个或一部分),发现新增的测试不能通过;

3. 做一些小小的改动,尽快地让测试程序可运行,为此可以在程序中使用一些不合情理的方法;

4. 运行所有的测试,并且全部通过;

5. 重构代码,以消除重复设计,优化设计结构简单来说,就是不可运行/可运行/重构——这正是测试驱动开发的口号。

        上述定义来自百度百科,感谢 李彦宏。

        那么在实际开发中,我们怎么样才能做到这一步呢?我们以下面一个例子来说明:

.h 头文件

////////////////////////////////////////////////////////////////////////////////////////////////////////
/** 线程类*/
class ThreadX
{
public:
	ThreadX(int nPriority = NormalPriority);
	~ThreadX();

    bool isFinished() const;
	bool isRunning() const;

	void start();
    void stop();

    /** 线程函数*/
	static void TH_WORK(void *pContex);

protected:
	/** run,inhrit for thread function*/
	virtual void run()=0;

private:
	bool _beginThread();

	THREAD_HANDLE  m_hThread;   /**< 线程句柄*/
	int            m_nPriority; /**< 优先级*/
	volatile int   m_nRunStatu; /**< 运行状态-0:finished  1:running  2:inFinish*/
};

.cpp文件

/** Constructor*/
ThreadX::ThreadX(int nPriority)
    :m_nPriority(nPriority)
    ,m_nRunStatu(0)
{
}
ThreadX::~ThreadX()
{
	stop();
}

bool ThreadX::isFinished() const
{
	return 0==m_nRunStatu;
}
bool ThreadX::isRunning() const
{
	return 1==m_nRunStatu;
}

/** start thread*/
void ThreadX::start()
{
	if (2==m_nRunStatu) 
	{
        //wait(); // todo,wait time until
        return;
    }
	if( 1==m_nRunStatu || !_beginThread() )
		return;
}

/** stop thread*/
void ThreadX::stop()
{
	if (INVALID_TH_HANDLE == m_hThread)
		return;

	m_nRunStatu = 2; // inFinish

#if (defined _WIN32) || (defined _WINDOWS_)
	WaitForSingleObject(m_hThread, INFINITE);
#else
	pthread_join(m_hThread, 0); 
#endif
	
	m_hThread = INVALID_TH_HANDLE;

    m_nRunStatu = 0; // thread is finished
}

/** 线程函数*/
void ThreadX::TH_WORK(void *pContex)
{
	ThreadX *pThis = (ThreadX *)pContex;

    pThis->run(); // 执行运行线程
}

bool ThreadX::_beginThread()
{
	// thread is running now
	m_nRunStatu = 1;

#if (defined _WIN32) || (defined _WINDOWS_)
	m_hThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)TH_WORK, this,0,NULL);
	if (0 == m_hThread)// 创建监听线程失败
	{
		m_nRunStatu = 0;
		return false;
	}
#else
	/*
	sched_param param;
	param.sched_priority = sched_get_priority_max(SCHED_RR);
	pthread_attr_t attr;
	pthread_attr_init(&attr);
	pthread_attr_setschedpolicy(&attr, SCHED_RR); // SCHED_OTHER(default), [SCHED_FIFO,SCHED_RR](real time for super user only)
	pthread_attr_setschedparam(&attr, &param);
	int nRet = pthread_create(&m_hThread, &attr, TH_WORK, this);
	*/
    // 默认属性为非绑定、非分离、默认1MB堆栈、与父进程有相同优先级。
	int nRet = pthread_create(&m_hThread, NULL, TH_WORK, this);
	if (0 != nRet) // 创建监听线程失败
	{
		m_nRunStatu = 0;
		return false;
	}
#endif

	return true;
}
        对于上面的代码如何测试其准确性呢?不要等到写完代码才想起来测试,拜托!(我估计99%的人都听不进去这个)

        你应该先这样写:

#include "ThreadX.h"

int main(int argc, char **argv)
{
	ThreadX th1();
	ThreadX th2();

	th1.start();
	th2.start();

	th1.stop();
	th2.stop();

	system("pause");
	return 0;
}
        没错,先写完上述测试例之后,再去填充 ThreadX 吧,ThreadX 里的函数记得打上Log,用于测试结果显示!
没有更多推荐了,返回首页