论坛首页 综合技术论坛

一种新的单元测试的方法

浏览 18055 次
该帖已经被评为良好帖
作者 正文
   发表时间:2008-10-25  
来自http://www.javalinux.it/wordpress/?p=116
个人很认同文章中的这个想法,因此翻译过来。不能保证翻译得正确,因此看原文还是更好的选择。
也希望各位能指出翻译中的错误,

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

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

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

BDD不仅是一个新的写测试的方法,也是一个按约定的新的设计形式
    引用behaviour-driven.org来开始我的介绍:
BehaviourDrivenDevelopment grew out of a thought experiment based on NeuroLinguisticProgramming techniques. The idea is that the words you use influence the way you think about something
BDD产生自一个基于神经语言规划学技术的实验的想法。这个想法是你用的语言会影响你考虑问题的方式。
     这个想法是需要程序员关注用来描述一个测试类或者方法的词语,因为所选的词语将影响到他们对这个问题的关注点。实际上,采用BDD方法写测试类时,相比这些被测试的类/方法本身,我们将更加关注这些被测试类/方法的行为。当然,这也将改变很多我们写测试代码的方式,比如,我们将对一个方法测试多次来验证每个行为是否正确。
     OK, what if I don’t believe on Neuro Linguistic Programming(神经语言规划学)?是否我还不相信神经语言规划学。好的,从一个纯粹的开发者角度来看,我们根据我们的类和方法的行为作为约定来命名测试,因此,这个测试结果将是绝对清晰的(比如,
”shouldAcceptNullValue fails”就是一个清楚的表达,并不需要复杂的报告)。让我们提供一个简单的例子:
@Test( expected = IllegalArgumentException.class )
    public void shouldNotPermitMethodNull() throws Exception {
    [..]
    }
    @Test( expected = IllegalArgumentException.class )
    public void shouldNotPermitEndPointNull() throws Exception {
    }
    @Test
    public void shouldInitWebParams() throws Exception {
    }
    @Test
    public void getHoldersResultShouldReturnHolderForRightParameters() throws Exception {
    }
    @Test
    public void getHoldersResultShouldIgnoreUnknowntParameters() throws Exception {
    }
    @Test
    public void getHoldersResultShouldIgnoreINParameters() throws Exception {
    }
    @Test
    public void shouldRuninvokeForOneWayMethod() throws Exception {
    }
    @Test
    public void shouldRuninvokeForMethods() throws Exception {
    }
    @Test
    public void shouldRuninvokeForMethodsApplyingMapping() throws Exception {
    }

    你需要一个简单的关于如何使用BDD成功应用到java上的介绍吗(最初的主意来自Ruby’s RSpec)?可以先看看excellent post.
那么BDD就够了吗?
    答案当然是no.BDD是很强大,你可以试一下,但是如果你真正的用于实践,你就会发现这些测试类和被测试类很快就会失去其联系。在BDD中,不仅仅测试方法不再是testXXX,那些测试类也可能不再是按约定取的名称。或者,你可能针对同一个被测试类/方法,会拥有多个测试的方法。例如之前的那个例子,当然不是很贴切,可能一个具体的测试类,比如来测试getHolder方法的行为的测试类更好一点。
你需要帮助吗?这是我的新的项目TestedBy
    通过注释来标注被测试类和方法,来使其与测试类和方法联系在一起,怎么样?不是太坏,你说呢?
我的想法或多或少从这里开始,但是不仅限于一个注释。我很高兴这个主意,因此我决定开始一个新的开源项目来提供测试工具,用来把被测试类放到中心的位置。
    继续看,我将展示给你如何通过注释和相关工具来改变你测试的方法。
    TestedBy目标在于改变观察测试类和被测试类的角度。我们可以把被测试的类(项目中最重要的类)放在中心位置,并可从这些类连接到他们的测试类和方法。一个代码快照可能抵得上更多的解释:
public class TestedBySample {
 
    /**
     * @param args
     */
    public static void main( String[] args ) {
        TestedBySample sample = new TestedBySample();
        System.out.print(sample.add(1, 2));
 
    }
 
    @TestedBy( testClass = "it.javalinux.testedby.TestedBySampleTest", testMethod = "addShouldWork" )
    public int add( int i,
                    int j ) {
        return i + j;
    }
    @TestedByList( {@TestedBy( testClass = "it.javalinux.testedby.TestedBySampleTest", testMethod = "addShouldWork" ),
        @TestedBy( testClass = "it.javalinux.testedby.TestedBySampleTest", testMethod = "addShouldWork2" )} )
    public int add2( int i,
                     int j ) {
        return i + j;
    }
 
}

    很好吧,但是它确实改变了你单元测试的方法?我觉得是,怎么样,至少在两个方面。
1,通过接口和约束来设计Design by interface and contracts
    软件设计中著名的词语说“Design by interface”,仍然在我耳边响起。因此你也当然可以“Test interface”。
很多正式的“Design by interface”意味着定义好你的接口,并且接着实现它们(可能大部分时间是由不同的人或者公司来实现)。总之,你能够影响到的只是如一个强类型语言,例如java能为你提供的那样:一个接口的实现必须遵循接口的签名,类型和参数变量保持一致。你不能控制到那些具体的行为。当然,这也是一个API设计上的一个很大的局限性,因为可能导致实现完全不可预期的行为。
    这里我就引出Eiffell language 和他的 design by contract (DbC)方法,约定来引导继承过来的特征的重新定义。在java中有很多工具支持Dbc,但是我的想法是我们已经有一个很强大的方式来测试方法上的约束,并且使用更少的代码来清楚行为:Unit Tests单元测试。
    使用TestedBy,测试在接口(或者父类)上声明,所有实现类都能运行这些测试,来验证这些实现类的行为,是符合父类类型上的行为和约束定义的。API设计者不仅提供接口,也提供一套验证该接口行为的测试类。因此每个实现者需要提供他们自己的实现,并且运行API设计者提供的测试,来保证实现类不仅是类型安全的,也是行为安全的。TestedBy传递给测试类一个被测试接口的实体类,并调用接口定义好的测试。
    这是一个使用TestedBy的例子:我们已经添加了@TestedBy注释在API接口,并提供一个@BeforeTestedBy注释来提供一个接口的实例。TestedBy将在实现类APIImplOne和APIImplTwo上运行shouldAddTwoAndThree,测试在第一个类上成功而在第二个类上失败。
public interface APIInterface {
   @TestedBy( testClass = "it.javalinux.testedby.APITest", testMethod = "shouldAddTwoAndThree" )
   public int add(int a, int b);
}
 
public class APIImplOne {
   public int add(int a, int b) {
	return a + b;
}
 
public class APIImplTwo {
   public int add(int a, int b) {
	return a - b;
}
 
public class APITest {
 
  private APIInterface instance;
 
  @BeforeTestedBy
  public beforeTestedBy(APIInterface instance) {
	this.instance = instance;
  } 	  
 
  public void shouldAddTwoAndThree() {
	assertThat(instance.add(3,2), is(5));
  }
 
}

    Of course I hava kept the example simple,but TestedBy has some more annotations ,for instance factory of classes under test can be specified when simple reflective invocation of no argument constructor isn’t enough。
    当然,我使例子尽量简单,但是TestedBy有更多的注释,比如当没有参数的构造函数的简单反射调用时,被测试类的实例工厂能被指定,这些注释还远远不够。
2,在当前工作的类上运行测试
    你写一个测试类来检验代码的正确性。很多时候,你写单元测试来保证代码的改变不会影响正在工作的代码,或者其他同事正在用的代码。你如何做呢?你可能改变你的代码,然后跑所有的涉及到修改的类的测试类。然后,最后你跑所有的测试来保证你没有影响到其他代码。
    那么让你的IDE第一步做什么呢?它编译你修改的类,并同时运行对应的测试类?既然你或者你的IDE能够运行测试类,testedBy让这个成为可能。一个eclipse插件就能做这些工作,针对你修改的类来运行相应的测试类,以验证没有破坏你的测试集,不仅仅是在编译阶段,也能在你写代码的时候。而且这个也不会太费时,因为插件能够根据TestedBy的注释来只运行很少的测试类,即那些已经修改过的类/方法对应的测试类。
    而且,运行测试类,能够得到一些清楚的报告比如:
"ClassUnderTest.methodUnderTest shouldThrowExceptionWithNullParameter doen't pass"

或者更好的:
"Failure: methodUnderTest in ClassUnderTest doesn'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点。
6,Ant /maven 任务/插件来从被测试类运行测试,这个工具将管理并平稳的使用(用来保证测试类和注释间的同步将很重要):
    A,反转引擎:从存在的测试类开始,添加TestedBy注释到被测试类中。
    B,从TestedBy注释中开始运行测试类,来验证他们能运行所有的测试,并且没有注释指向不存在的测试。
    C,从注释开始生成(空的)测试类和方法。
    当然,这还不够,但是,我想这能给你一个想法。我现在正在做这些想法背后的事情,但是我需要在把他们呈现给社区之前把它们设计得更好。
Conclusions总结
     你对这个方法有兴趣吗?让我知道你的想法,并且跟你的朋友分享这个帖子…我能从这个帖子得到越多的反馈,我就能更好的做好它。

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

    订购这个feeds.我将会经常发表声明,并且其他的回复也会关注这个想法,以使之得到不断的修正。
    你想贡献一份力量吗?看看this page来更好的理解我们需要哪些帮助。





   发表时间:2008-11-04  
没看懂,不过,先了解一下behaviour-driven。
谢谢楼主分享!
0 请登录后投票
   发表时间:2008-11-17  
建议去看看rubyonrails的BDD,尤其是rspec,亲自写几段测试代码,java在这方面有些臃肿。
0 请登录后投票
   发表时间:2008-11-17  
有意思,不错。。
0 请登录后投票
   发表时间:2008-11-27  
原来只听过Junit而且都还没有用过,现在又有什么这个东西看,发展的太快了啊!
0 请登录后投票
   发表时间:2008-12-11  
有点意思。但是还要做的工作还是有的。 至少有各和junit的exlipse UI把!看不到绿色条,不够直观
0 请登录后投票
   发表时间:2009-01-04  
给我的感觉就是,将测试类和被测试类通过注释绑定到一起了,能够更好的查找每个类相应的测试代码。  测试的开始是根据被测试的代码中的注释转到测试代码中,这个是方便了……!
0 请登录后投票
   发表时间:2009-01-06  
看不懂,但是真的希望有好的测试工具,使用越简单越好
0 请登录后投票
   发表时间:2009-02-17  
看不怎么懂!
0 请登录后投票
   发表时间:2009-02-17  
其实就像guice通过annotation来连接接口和具体的实现类,
TestedBy也是希望通过annotation来连接测试类和被测试的类,
从而得到象guice那样的好处,能够更方便的跟踪代码。
0 请登录后投票
论坛首页 综合技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics