Android开发笔记之:对实践TDD的一些建议说明
程序员文章站
2023-12-09 22:47:45
最近部分采用了tdd的方法来开发一个模块,小有收获特此总结一下:1. tdd的基本原则tdd的最核心思想就是先明确需求,且用代码的方式量化,明确需求标准,然后进行编码实现以...
最近部分采用了tdd的方法来开发一个模块,小有收获特此总结一下:
1. tdd的基本原则
tdd的最核心思想就是先明确需求,且用代码的方式量化,明确需求标准,然后进行编码实现以达成由代码测试来衡量的标准。
那么它要求,先把需要标准写出来,每次只写一个。编码实现通过达到,并刚好满足这个标准。这样一点一点的迭代。
这样有三个好处:一个是先明确标准,不至于我们迷失主题,偏离方向。有标准在检测,保证代码是正确的。仅满足当前测试,不至于过早优化和过度设计。
2. tdd的难点
难点在于如何设计这个测试标准,
1)让它足够小,是一个需求单元;
2)成为标准,也就是如何检测正确性;
3)就是如何在最大程度模拟真实运行的场景,而不是为了测试而写出许多额外的工具,也就是说测试应该跟真实的项目代码一样,不应该有多余的东西。
这关键在于要分析挖掘需求,并细化需求。如果都像书中的例子那样测试一些api那倒是很好写,因为测试代码跟真实的app代码用一样的方式来调用api,而且api的功能也会有明确的描述。但现实情况并非如此,比如很多框架就很难测试,很多对象和创建和控制都是由框架来做,你无法像控制。这就导致了很难写测试用例。
还有就是多线程,由于线程带来的不确定性,有很多伪失败,这可以参考书,书中有方法。
3. android中的tdd
老实说,在android完全用tdd的方法来开发是不可能的。原因如下:
1. android中的应用程序主要结构是四大组件:service,activity和provider和receiver这四东西的创建和销毁都是由框架来控制。所以你不可能像书中例子那样去测试它们,因为有些限制让你无法用代码来测试。
2. 有些东西是系统框架的回调或者很基本东西根本不用写testcase。比如view的click/longclick/touch事件的处理之类的,或者activity的生命周期回调,或者optionsmenu/contextmenu之类的。
3. sdk中的用于测试的api功能太弱
这就导致了,为了测试一个小功能需要做很多工作和写很多代码,远大于直接实现。比如测试一个弹出的dialog,如果直接实现很容易;但如果用代码来测试就要多3,4倍的工作量,远大于直接实现。
4. 那么在android中应该如何运用好tdd呢?以下是一些建议:
1. 使用robotium
这是强大的工具,它比sdk中的东西可是方便的很多比如searchtext,clickmenu之类的接口非常的方便和实用。
2. 自动测试+手动测试
同样要遵循原则,但是对于测试用例,没有必要完全用代码来写,可以部分手动测试:一般的原则来讲如果自动测试比较方便的实现就写testcase,如果手动测试很方便就手动测试,这没有死规则要看具体的情况。
比如,view的事件,activity的事件,activity的menu,dialog之类的与交互相关的东西,以及跨应用交互的用例最好手动来测试,因为这些东西用代码来测试更麻烦。
但对于一些涉及数值,计算,量化等就用代码来做。比如下载一个文件,设定好路径后就可以直接用file对象来检测文件是否下载成功。
3. provider必须要测试
provider提供的是api,它非常好测试也容易写,又是一个项目的基本设施,所以必须要好好测试,否则如果在activity上某条数据有问题,你必须要确定是显示上出了问题还是provider里出了问题。通常crud必须测试,还有就是where语句,以及逆向测试,必须要检测uri的合法性等,还有就是要检测对特殊字符的处理,比如'和"。
4. 除service和activity以外的东西,特别是自己实现的类似api的类,如果里面涉及一些业务逻辑也要进行测试。这就跟书中的例子差不多了,测试的难易成就也取决于业务的分解,设计和耦合度了。
5. 用反射来测试类的内部
对于service和activity虽然可以在testcase中拿到它的实例,但是service和activity是一个组件单元在实际中并不会public太多的接口,它们是处于最顶端的调用其他接口,而自己不会,也不应该公开接口给别人用,原因就是它们的创建和生命周期的管理都是由系统控制的,别处不应该有太多对它们的引用。
那么当要测试service和activity内部时怎么办呢?比如要测试某个service内部的一个int[] mplaylistqueue。我们总不能为了写case而在service中加接口吧!这时就要用反射机制来取出这个成员的实例,然后检查它的数据。
6. 有些东西必须手动测试,自动化无法完成
testcase是有特殊的context和mockobject的,它是对真实android运行的一个最大化的模拟,它并不跟应用真正运行时的情况完全一样!而且由于permission的原因,某些事情instrumentation是无法做的,比如alarm,日期等instrumentation是无权限更改的。这些必须要靠手动测试。
还有就是service和activity的初始化和销毁,特别是销毁,没办法测试,也就是说对于ondestroy()里面的东西,还真的不好去测试。第一,你不知道它何时被回调到;第二,执行到它时对象快被销毁了,你持有的引用不一定有效了;第三,成员对象是否都有效也无法得知。对于ondestroy只能通过调试手段手动的去测试。
总之,在我看来,tdd的核心思想是测试先来,实现后来。但如何测试并没有列规定非要用代码,所以根据实际情况,选择最佳的测试手段。
1. tdd的基本原则
tdd的最核心思想就是先明确需求,且用代码的方式量化,明确需求标准,然后进行编码实现以达成由代码测试来衡量的标准。
那么它要求,先把需要标准写出来,每次只写一个。编码实现通过达到,并刚好满足这个标准。这样一点一点的迭代。
这样有三个好处:一个是先明确标准,不至于我们迷失主题,偏离方向。有标准在检测,保证代码是正确的。仅满足当前测试,不至于过早优化和过度设计。
2. tdd的难点
难点在于如何设计这个测试标准,
1)让它足够小,是一个需求单元;
2)成为标准,也就是如何检测正确性;
3)就是如何在最大程度模拟真实运行的场景,而不是为了测试而写出许多额外的工具,也就是说测试应该跟真实的项目代码一样,不应该有多余的东西。
这关键在于要分析挖掘需求,并细化需求。如果都像书中的例子那样测试一些api那倒是很好写,因为测试代码跟真实的app代码用一样的方式来调用api,而且api的功能也会有明确的描述。但现实情况并非如此,比如很多框架就很难测试,很多对象和创建和控制都是由框架来做,你无法像控制。这就导致了很难写测试用例。
还有就是多线程,由于线程带来的不确定性,有很多伪失败,这可以参考书,书中有方法。
3. android中的tdd
老实说,在android完全用tdd的方法来开发是不可能的。原因如下:
1. android中的应用程序主要结构是四大组件:service,activity和provider和receiver这四东西的创建和销毁都是由框架来控制。所以你不可能像书中例子那样去测试它们,因为有些限制让你无法用代码来测试。
2. 有些东西是系统框架的回调或者很基本东西根本不用写testcase。比如view的click/longclick/touch事件的处理之类的,或者activity的生命周期回调,或者optionsmenu/contextmenu之类的。
3. sdk中的用于测试的api功能太弱
这就导致了,为了测试一个小功能需要做很多工作和写很多代码,远大于直接实现。比如测试一个弹出的dialog,如果直接实现很容易;但如果用代码来测试就要多3,4倍的工作量,远大于直接实现。
4. 那么在android中应该如何运用好tdd呢?以下是一些建议:
1. 使用robotium
这是强大的工具,它比sdk中的东西可是方便的很多比如searchtext,clickmenu之类的接口非常的方便和实用。
2. 自动测试+手动测试
同样要遵循原则,但是对于测试用例,没有必要完全用代码来写,可以部分手动测试:一般的原则来讲如果自动测试比较方便的实现就写testcase,如果手动测试很方便就手动测试,这没有死规则要看具体的情况。
比如,view的事件,activity的事件,activity的menu,dialog之类的与交互相关的东西,以及跨应用交互的用例最好手动来测试,因为这些东西用代码来测试更麻烦。
但对于一些涉及数值,计算,量化等就用代码来做。比如下载一个文件,设定好路径后就可以直接用file对象来检测文件是否下载成功。
3. provider必须要测试
provider提供的是api,它非常好测试也容易写,又是一个项目的基本设施,所以必须要好好测试,否则如果在activity上某条数据有问题,你必须要确定是显示上出了问题还是provider里出了问题。通常crud必须测试,还有就是where语句,以及逆向测试,必须要检测uri的合法性等,还有就是要检测对特殊字符的处理,比如'和"。
4. 除service和activity以外的东西,特别是自己实现的类似api的类,如果里面涉及一些业务逻辑也要进行测试。这就跟书中的例子差不多了,测试的难易成就也取决于业务的分解,设计和耦合度了。
5. 用反射来测试类的内部
对于service和activity虽然可以在testcase中拿到它的实例,但是service和activity是一个组件单元在实际中并不会public太多的接口,它们是处于最顶端的调用其他接口,而自己不会,也不应该公开接口给别人用,原因就是它们的创建和生命周期的管理都是由系统控制的,别处不应该有太多对它们的引用。
那么当要测试service和activity内部时怎么办呢?比如要测试某个service内部的一个int[] mplaylistqueue。我们总不能为了写case而在service中加接口吧!这时就要用反射机制来取出这个成员的实例,然后检查它的数据。
6. 有些东西必须手动测试,自动化无法完成
testcase是有特殊的context和mockobject的,它是对真实android运行的一个最大化的模拟,它并不跟应用真正运行时的情况完全一样!而且由于permission的原因,某些事情instrumentation是无法做的,比如alarm,日期等instrumentation是无权限更改的。这些必须要靠手动测试。
还有就是service和activity的初始化和销毁,特别是销毁,没办法测试,也就是说对于ondestroy()里面的东西,还真的不好去测试。第一,你不知道它何时被回调到;第二,执行到它时对象快被销毁了,你持有的引用不一定有效了;第三,成员对象是否都有效也无法得知。对于ondestroy只能通过调试手段手动的去测试。
总之,在我看来,tdd的核心思想是测试先来,实现后来。但如何测试并没有列规定非要用代码,所以根据实际情况,选择最佳的测试手段。