一种新的单元测试的方法

<!--[if gte mso 9]><![endif]--><!--[if gte mso 9]><![endif]--><!--[if gte mso 10]><! /* Style Definitions */ table.MsoNormalTable{mso-style-name:普通表格;mso-tstyle-rowband-size:0;mso-tstyle-colband-size:0;mso-style-noshow:yes;mso-style-parent:"";mso-padding-alt:0cm 5.4pt 0cm 5.4pt;mso-para-margin:0cm;mso-para-margin-bottom:.0001pt;mso-pagination:widow-orphan;font-size:10.0pt;font-family:"Times New Roman";mso-fareast-font-family:"Times New Roman";mso-ansi-language:#0400;mso-fareast-language:#0400;mso-bidi-language:#0400;}-->

    一种新的单元测试的方法意味着什么?难道说Junit或者TestNG还不够好? Junit (这里我提及到它因为它简单,但是在我的讨论中,TestNG 也一样简单 from here on I’ll nominate it only for briefness,but TestNG is thesame for my discussion)把测试类作为重点,并且所有的测试都是从那里开始。这意味着事实上,被测试类被认为仅仅存在于测试代码中,程序员只能通过它们使用的名字惯例来找到这些被测试类。

  在旧版本的Junit中,你设计的测试类,需要强制继承Junit框架中的类,并且将调用的方法需要用"test"开头。因此按这个惯例来命名测试类和测试方法,并把他们与被测试的类和方法连接起来。我想你会同意,TestNGJunit4给了我 们更多的自由,去掉这些要求。不管怎样,被测试的类和方法和我们的测试类仍然是这样逻辑的联系在一起,并且很多测试类仍然遵循那些旧的惯例。

  但是有很多更好的方式来命名类和方法!我来介绍BDD(Behaviour Driven Development),请注意BDD不是这篇文章的重点,但是它使我这篇文章更加完美。因此,让我们先用少许文字了解BDD吧。


BDD不仅是一个新的写测试的方法,也是一个按约定的新的设计形式

  引用behaviour-driven.org来开始我的介绍:

   BehaviourDrivenDevelopment grew out of a thought experiment based onNeuroLinguisticProgramming techniques. The idea is that the words you use influencethe way you think about something

  BDD产生自一个基于神经语言规划学技术的实验的想法。这个想法是你用的语言会影响你考虑问题的方式。

   这个想法是需要程序员关注用来描述一个测试类或者方法的词语,因为所选的词语将影响到他们对这个问题的关注点。实际上,采用BDD方法写测试类时,相比 这些被测试的类/方法本身,我们将更加关注这些被测试类/方法的行为。当然,这也将改变很多我们写测试代码的方式,比如,我们将对一个方法测试多次来验证 每个行为是否正确。

  OK, what if I don’t believe on Neuro Linguistic Programming(神经语言规划学)?是否我还不相信神经语言规划学。好的,从一个纯粹的开发者角度来看,我们根据我们的类和方法的行为作为约定来 命名测试,因此,这个测试结果将是绝对清晰的(比如,“shouldAcceptNullValue fails”就是一个清楚的表达,并不需要复杂的报告)。让我们提供一个简单的例子:

  Java代码

    1. @Test( expected = IllegalArgumentException.class )

  2. public void shouldNotPermitMethodNull() throws Exception {

  3. [..]

  4. }

  5. @Test( expected = IllegalArgumentException.
class )

  6. public void shouldNotPermitEndPointNull() throws Exception {

  7. }

  8. @Test

  9.
public void shouldInitWebParams() throws Exception {

  10. }

  11. @Test

  12.
public voidgetHoldersResultShouldReturnHolderForRightParameters() throwsException {

  13. }

  14. @Test

  15.
public void getHoldersResultShouldIgnoreUnknowntParameters()throws Exception {

  16. }

  17. @Test

  18.
public void getHoldersResultShouldIgnoreINParameters() throws Exception {

  19. }

  20. @Test

  21.
public void shouldRuninvokeForOneWayMethod() throws Exception {

  22. }

  23. @Test

  24.
public void shouldRuninvokeForMethods() throws Exception {

  25. }

  26. @Test

  27.
public void shouldRuninvokeForMethodsApplyingMapping() throws Exception {

  28. }

   你需要一个简单的关于如何使用BDD成功应用到java上的介绍吗(最初的主意来自Ruby’s RSpec)?可以先看看excellent post.


那么BDD就够了吗?

  答案当然是NOBDD是很强大,你可以试一下,但是如果你真正的用于实践,你就会发现这些测试类和被测试类很快就会失去其联系。在BDD中,不仅仅测试方法不再是testXXX,那些测试类也可能不再是按约定取的名称。或者,你可能针对同一个被测试类/方法,会拥有多个测试的方法。例如之前的那个例子,当然不是很贴切,可能一个具体的测试类,比如来测试getHolder方法的行为的测试类更好一点。


你需要帮助吗?这是我的新的项目TestedBy

  通过注释来标注被测试类和方法,来使其与测试类和方法联系在一起,怎么样?不是太坏,你说呢?我的想法或多或少从这里开始,但是不仅限于一个注释。我很高兴这个主意,因此我决定开始一个新的开源项目来提供测试工具,用来把被测试类放到中心的位置。

  继续看,我将展示给你如何通过注释和相关工具来改变你测试的方法。

  TestedBy目标在于改变观察测试类和被测试类的角度。我们可以把被测试的类(项目中最重要的类)放在中心位置,并可从这些类连接到他们的测试类和方法。一个代码快照可能抵得上更多的解释:

  Java代码

    1. public class TestedBySample {

  2.

  3.
/**

  4. * @param args

  5. */

  6. public static void main( String[] args ) {

  7. TestedBySample sample =new TestedBySample();

  8.System.out.print(sample.add(1, 2));

  9.

  10. }

  11.

  12. @TestedBy( testClass = "it.javalinux.testedby.TestedBySampleTest",testMethod = "addShouldWork" )

  13.
public int add( int i,

  14. int j ) {

  15. return i + j;

  16. }

  17. @TestedByList( {@TestedBy( testClass ="it.javalinux.testedby.TestedBySampleTest", testMethod = "addShouldWork"),

  18. @TestedBy( testClass ="it.javalinux.testedby.TestedBySampleTest", testMethod ="addShouldWork2" )} )

  19.
public int add2( int i,

  20. int j ) {

  21. return i + j;

  22. }

  23.

  24. }

  很好吧,但是它确实改变了你单元测试的方法?我觉得是,怎么样,至少在两个方面。


1、通过接口和约束来设计Design by interface andcontracts

  软件设计中著名的词语说“Design by interface”,仍然在我耳边响起。因此你也当然可以“Testinterface”

   很多正式的“Design by interface”意味着定义好你的接口,并且接着实现它们(可能大部分时间是由不同的人或者公司来实现)。总之,你能够影响到的只是如一个强类型语 言,例如java能为你提供的那样:一个接口的实现必须遵循接口的签名,类型和参数变量保持一致。你不能控制到那些具体的行为。当然,这也是一个API设 计上的一个很大的局限性,因为可能导致实现完全不可预期的行为。

  这里我就引出Eiffell language 和他的 design by contract(DbC)方法,约定来引导继承过来的特征的重新定义。在java中有很多工具支持Dbc,但是我的想法是我们已经有一个很强大的方式来测试方法上的约 束,并且使用更少的代码来清楚行为:Unit Tests单元测试。

  使用TestedBy,测试在接口(或者父类)上声明,所有实现类都能运行这些测试,来验证这些实现类的行为,是符合父类类型上的行为和约束定义的。API设计者不仅提供接口,也提供一套验证该接口行为的测试类。因此每个实现者需要提供他们自己的实现,并且运行API设计者提供的测试,来保证实现类不仅是类型安全的,也是行为安全的。TestedBy传递给测试类一个被测试接口的实体类,并调用接口定义好的测试。

   这是一个使用TestedBy的例子:我们已经添加了@TestedBy注释在API接口,并提供一个@BeforeTestedBy注释来提供一个接 口的实例。TestedBy将在实现类APIImplOneAPIImplTwo上运行shouldAddTwoAndThree,测试在第一个类上成功而在第二个类上失败。

  Java代码

    1. public interface APIInterface {

  2. @TestedBy( testClass ="it.javalinux.testedby.APITest", testMethod ="shouldAddTwoAndThree" )

  3.
public int add(int a, int b);

  4. }

  5.

  6.
public class APIImplOne {

  7. public int add(int a, int b) {

  8. return a + b;

  9. }

  10.

  11.
public class APIImplTwo {

  12. public int add(int a, int b) {

  13. return a - b;

  14. }

  15.

  16.
public class APITest {

  17.

  18.
private APIInterface instance;

  19.

  20. @BeforeTestedBy

  21.
public beforeTestedBy(APIInterface instance) {

  22. this.instance = instance;

  23. }

  24.

  25.
public void shouldAddTwoAndThree() {

  26.assertThat(instance.add(3,2), is(5));

  27. }

  28.

  29. }

   当然,我使例子尽量简单,但是TestedBy有更多的注释,比如当没有参数的构造函数的简单反射调用时,被测试类的实例工厂能被指定,这些注释还远远不够。


2、在当前工作的类上运行测试

  你写一个测试类来检验代码的正确性。很多时候,你写单元测试来保证代码的改变不会影响正在工作的代码,或者其他同事正在用的代码。你如何做呢?你可能改变你的代码,然后跑所有的涉及到修改的类的测试类。然后,最后你跑所有的测试来保证你没有影响到其他代码。

   那么让你的IDE第一步做什么呢?它编译你修改的类,并同时运行对应的测试类?既然你或者你的IDE能够运行测试类,testedBy让这个成为可能。一个eclipse插件就能做这些工作,针对你修改的类来运行相应的测试类,以验证没有破坏你的测试集,不仅仅是在编译阶段,也能在你写代码的时候。而且这个也不会太费时,因为插件能够根据TestedBy的注释来只运行很少的测试类,即那些已经修改过的类/方法对应的测试类。

  而且,运行测试类,能够得到一些清楚的报告比如:

  Java代码

  1. "ClassUnderTest.methodUnderTestshouldThrowExceptionWithNullParameter doen't pass"

  或者更好的:

  Java代码

  1. "Failure: methodUnderTest in ClassUnderTestdoesn't throw exception with null parameter, but it should!"

  当然,你能够拒绝这种针对类/方法名称 (如果你想的话)提供的便利,自己从整个工程中去找那些测试类,根据需要自己来测试。

  当然针对所有的类来运行所有的测试类也是可能的,你的测试也能够在Junit上运行,因为TestedBy是基于Junit测试的。


一些特点

  让我列举在我心中TestedBy的特点吧:

   1、第一个可能是最没有价值的,但是也是最有用的:在你的IDE中,你能够导航你的源代码到对应的测试代码。这个特性将在eclipse4.4的时候发 布,它能够根据类名称导航,即使类名处在一个字符串中。当然,这只是一个开始,一个专门的eclipse插件将被开发用来导航代码,以及被测试代码的测试 代码树形结构,针对打开/修改的类(见第3)执行测试代码,等等。我不是一个eclipse开发的大师,因此非常欢迎哪位能够在这方面做出贡献。

   2、你将在你的java文档中找到这些注释,考虑这是一个多好的事情,如果你使用BDD方式开发,定义测试方法形如 shouldNotAcceptNull()或者shouldThrowsExceptionIfEmpty等等。使用BDD方法,事实上你能够定义和检查约束,通过使用TestedBy的注释,在JavaDoc文档中把这些展示给你的API用户。

  3、你能够从被测试类开始运行测试,而不是测试类。我在之前就说过这些了。

  4、通过接口和约束的设计。我已经在上文中专门介绍了这些。

  5、测试类自动生成。一个工具(ant,maven,或者eclipse工具)能用我们的TestedBy注释来生成测试类。见6点。

  6Ant /maven 任务/插件来从被测试类运行测试,这个工具将管理并平稳的使用(用来保证测试类和注释间的同步将很重要)

  A. 反转引擎:从存在的测试类开始,添加TestedBy注释到被测试类中。

  B. TestedBy注释中开始运行测试类,来验证他们能运行所有的测试,并且没有注释指向不存在的测试。

  C. 从注释开始生成(空的)测试类和方法。

  当然,这还不够,但是,我想这能给你一个想法。我现在正在做这些想法背后的事情,但是我需要在把他们呈现给社区之前把它们设计得更好。


Conclusions总结

  你对这个方法有兴趣吗?让我知道你的想法,并且跟你的朋友分享这个帖子我能从这个帖子得到越多的反馈,我就能更好的做好它。

  现在问题是..目前这个项目的状态是什么?

  你可以看一下project homepage on javalinuxlabs. googlecode 上面,你可以在svn下面找到一些类。这不是这个工程的第一次实现它差不多是这个想法的证明。但是,我们需要更努力的工作来让它更快的派上用场。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值