我怎样才能改进我的junit测试

对我的junit测试看起来像一个长篇故事:

  • 我创建了4个用户
  • 我删除了1个用户
  • 我尝试使用已删除的用户登录并确保它失败
  • 我使用剩下的3个用户之一登录并validation我可以登录
  • 我从一个用户向另一个用户发送消息,并validation它是否显示在发件人的发件箱中以及接收者的收件箱中。
  • 我删除了这条消息

优点 :测试非常有效(非常擅长检测错误)并且非常稳定,因为它们只使用API​​,如果我重构代码,那么测试也会被重构。 由于我不使用“脏技巧”,例如在给定状态下保存和重新加载数据库,我的测试无视模式更改和实现更改。

缺点 :测试变得难以维护,测试中的任何更改都会影响其他测试。 测试运行8-9分钟,这对于持续集成非常有用,但对开发人员来说有点令人沮丧。 测试不能孤立运行,你可以做的最好的事情是在你感兴趣的测试运行后停止 – 但是你必须运行之前的所有测试。

你会如何改进我的测试?

unit testing应该 – 理想情况下 – 是独立的,并且能够以任何顺序运行。 所以,我建议你:

  • 将你的测试分解为独立的
  • 考虑使用内存数据库作为测试的后端
  • 考虑将每个测试或套件包装在最后回滚的事务中
  • 对unit testing进行分析,看看时间在哪里,并专注于此

如果创建一些用户并发送一些消息需要8分钟,性能问题可能不在测试中,而这可能是系统本身性能问题的症状 – 只有您的探查器确切知道!

[警告:我不认为这些类型的测试是’整合测试’,尽管我可能属于少数; 我认为这些类型的测试是function的unit testing ,一个TDD]

首先,了解您所拥有的测试是集成测试(可能访问外部系统并访问各种类)。 unit testing应该更加具体,这对已经构建的系统来说是一个挑战。 实现这一目标的主要问题通常是代码的结构方式:

即类紧密耦合到外部系统(或其他类)。 为了能够这样做,您需要以这样的方式构建类,以便您可以在unit testing期间实际避免命中外部系统。

更新1:阅读以下内容,并考虑最终的设计将允许您实际测试加密逻辑,而无需访问文件/数据库 – http://www.lostechies.com/blogs/gabrielschenker/archive/2009/01/30/ the-dependency-inversion-principle.aspx (不是在java中,但是很好地说明了这个问题)…还要注意你可以为读者/编写者做一个非常集中的集成测试,而不是一起测试它们。

我建议:

  • 逐步在您的系统中包含实际unit testing。 您可以在进行更改和开发新function时进行相应的重构。
  • 在执行上述操作时,请在适当的位置包含重点集成测试 确保您能够运行与集成测试分开的unit testing。
  • 考虑到您的测试接近于测试整个系统,因此与自动验收测试的不同之处仅在于它们在API的边界上运行。 考虑到这一点,请考虑与产品API的重要性相关的因素(如果它将在外部使用),以及您是否具有良好的自动验收测试覆盖率。 这可以帮助您了解在您的系统上拥有这些内容的价值,以及它们自然需要这么长时间的原因。 决定是否要在接口级别或接口+ api级别上测试整个系统。

更新2:根据其他答案,我想澄清一些关于做TDD的事情。 让我们说你必须检查一些给定的逻辑是否发送电子邮件,将信息记录在一个文件上,将数据保存在数据库中,并调用一个Web服务(不是我所知道的,但你开始为每个服务添加测试) 。 在每次测试中,您不想访问外部系统,您真正想要测试的是逻辑是否会调用您期望它执行的那些系统。 因此,当您编写一个测试来检查在创建用户时是否发送了电子邮件时,您测试的是逻辑是否调用了执行此操作的依赖项。 请注意,您可以编写这些测试和相关逻辑,而无需实际发送电子邮件的代码(然后必须访问外部系统以了解发送的内容…)。 这将帮助您专注于手头的任务,并帮助您获得一个分离的系统。 它还可以简化测试发送到这些系统的内容。

现在,您正在一种方法中测试很多东西(违反每次测试一次断言)。 这是一件坏事,因为当这些事情发生任何变化时,整个测试都会失败。 这导致它不能立即明显为什么测试失败以及需要修复的内容。 此外,当您有意更改系统的行为时,您需要更改更多测试以对应更改的行为(即测试是脆弱的)。

要知道什么样的测试是好的,有助于阅读有关BDD的更多内容: http : //dannorth.net/introducing-bdd http://techblog.daveastels.com/2005/07/05/a-new-look- at-test-driven-development / http://jonkruger.com/blog/2008/07/25/why-behavior-driven-development-is-good/

为了改进您提到的测试,我将使用这些上下文和测试方法名称将其拆分为以下三个测试类:

创建用户帐户

  • 在创建用户之前
    • 用户不存在
  • 创建用户时
    • 用户存在
  • 删除用户时
    • 用户不再存在

在登录

  • 当用户存在时
    • 用户可以使用正确的密码登录
    • 用户无法使用错误的密码登录
  • 当用户不存在时
    • 用户无法登录

发送消息

  • 当用户发送消息时
    • 邮件显示在发件人的发件箱中
    • 消息显示在接收方的收件箱中
    • 该消息不会出现在任何其他消息框中
  • 删除邮件时
    • 消息不再存在

您还需要提高测试速度。 你应该有一个覆盖良好的unit testing套件,它可以在几秒钟内运行。 如果运行测试花费的时间超过10-20秒,那么每次更改后您都会犹豫不决,并且会丢失一些运行测试的快速反馈。 (如果它与数据库对话,它不是unit testing,而是系统或集成测试,它们有其用途,但速度不够快,不能连续执行。)你需要通过模拟来破坏被测试类的依赖关系。 或stub他们。 同样根据您的描述,您的测试似乎不是孤立的,而是测试取决于先前测试引起的副作用 – 这是禁忌。 好的测试是第一次 。

减少测试之间的依赖关系。 这可以通过使用Mocks来完成。 Martin Fowler在模拟中谈到它不是存根 ,特别是为什么模拟可以减少测试之间的依赖关系。

您可以使用JExample ,它是JUnit的扩展,它允许测试方法具有由其他测试重用的返回值。 JExample测试使用Eclipse中的普通JUnit插件运行,并且还与普通的JUnit测试并行工作。 因此,迁移应该没有问题。 JExample使用如下

@RunWith(JExample.class) public class MyTest { @Test public Object a() { return new Object(); } @Test @Given("#a") public Object b(Object object) { // do something with object return object; } @Test @Given("#b") public void c(Object object) { // do some more things with object } } 

免责声明,我是JExample开发人员之一。

如果您使用TestNG,您可以通过各种方式注释测试。 例如,您可以将上面的测试注释为长时间运行 。 然后,您可以配置自动构建/持续集成服务器来运行这些服务器,但标准的“交互式”开发人员构建不会(除非他们明确选择)。

这种方法取决于开发人员定期检查您的持续构建,以便测试确实运行!

一些测试将不可避免地需要很长时间才能运行。 这个post中的评论重新开始。 表现都是有效的。 但是,如果您的测试确实需要很长时间,那么实用的解决方案就是运行它们,但不要让它们的耗时性质影响开发人员以避免运行它们。

注意:您可以通过(例如)以不同的方式命名测试并使您的连续构建运行特定的测试类子集来执行与JUnit类似的操作。

通过测试你描述的故事,你会有非常脆弱的测试。 如果只有一小部分function发生变化,那么整个测试可能会搞砸。 然后,您可能会更改受该更改影响的所有测试。

事实上,您所描述的测试更像是function测试或组件测试,而不是unit testing。 因此,您使用unit testing框架(junit)进行非unit testing。 在我看来,使用unit testing框架进行非unit testing没有错, 如果(并且只有)你知道它。

所以有以下选择:

  • 像其他用户已经建议的那样, 选择另一个支持“故事讲述”式测试的测试框架 。 您必须评估并找到合适的测试框架。

  • 使您的测试更像“unit testing”。 因此,您需要分解测试并可能更改当前的生产代码。 为什么? 因为unit testing旨在测试小单元代码(unit testing纯粹主义者建议一次只有一个类)。 通过这样做,您的unit testing变得更加独立。 如果更改一个类的行为,则只需更改相对少量的unit testing代码。 这使您的unit testing更加稳健。 在此过程中,您可能会发现当前代码不能很好地支持unit testing – 主要是因为类之间存在依赖关系。 这就是您还需要修改生产代码的原因。

如果您在项目中并且时间不够用,那么这两个选项可能对您没有任何帮助。 然后你将不得不忍受这些测试,但你可以尝试减轻你的痛苦:

  • 删除测试中的代码重复 :就像在生产代码中一样,消除代码重复并将代码放入辅助方法或辅助类中。 如果有变化,您可能只需要更改辅助方法或类。 这样你就会收敛到下一个建议。

  • 在测试中添加另一个间接层:生成辅助方法和辅助类,这些方法和辅助类在更高级别的抽象上运行。 它们应该作为测试的API。 这些助手正在调用您的生产代码。 你的故事测试应该只调用那些助手。 如果发生变化,您只需更改API中的一个位置,而无需触及所有测试。

API的示例签名:

 createUserAndDelete(string[] usersForCreation, string[] userForDeletion); logonWithUser(string user); sendAndCheckMessageBoxes(string fromUser, string toUser); 

对于一般的unit testing,我建议看一下Gerard Meszaros的XUnit测试模式 。

要在生产测试中打破依赖关系,请查看Michael Feathers的“有效使用遗留代码”

除了上述内容之外,还可以阅读一本关于TDD的好书(我可以推荐“适用于Java开发人员的TDD和验收TDD”)。 即使它从TDD的角度来看,也有很多关于编写正确的unit testing的有用信息。

找到在该地区拥有大量知识的人,并使用它们来弄清楚如何改进测试。

加入邮件列表来提问,只需阅读流量即可。 雅虎的JUnit列表(类似groups.yahoo.com/junit)。 JUnit世界中的一些推动者和振动者都在该名单上并积极参与。

获取unit testing的黄金规则列表,并将它们粘贴在您的(和其他)隔间墙上,例如:

  • 你永远不会访问外部系统
  • 你只能测试被测代码
  • 你只能一次测试一件事等。

由于其他人都在谈论结构,我会选择不同的观点。 这听起来像是一个很好的机会来分析代码以找到瓶颈并通过代码覆盖运行它以查看您是否遗漏了任何东西(考虑到运行它所花费的时间,结果可能很有趣)。

我个人使用Netbeans分析器,但其他IDE中也有一些,也可以单独使用。

对于代码覆盖我使用Cobertura ,但EMMA也有效(EMMA有一个Cobertura没有的烦恼……我忘了它是什么,它可能不再是一个问题)。 这两个是免费的,有付费的也是好的。