简体   繁体   English

如何改进我的 junit 测试

[英]How can I improve my junit tests

Right my junit tests look like a long story:对,我的 junit 测试看起来很长:

  • I create 4 users我创建了 4 个用户
  • I delete 1 user我删除了 1 个用户
  • I try to login with the deleted user and make sure it fails我尝试使用已删除的用户登录并确保它失败
  • I login with one of the 3 remaining user and verify I can login我使用剩下的 3 个用户之一登录并验证我可以登录
  • I send a message from one user to the other and verify that it appears in the outbox of the sender and in the inbox of the receiver.我从一个用户向另一个用户发送消息,并验证它是否出现在发件人的发件箱和收件人的收件箱中。
  • I delete the message我删除消息
  • ... ...
  • ... ...

Advantages : The tests are quite effective (are very good at detecting bugs) and are very stable, becuase they only use the API, if I refactor the code then the tests are refactored too.优点:测试非常有效(非常擅长检测错误)并且非常稳定,因为它们只使用 API,如果我重构代码,那么测试也会被重构。 As I don't use "dirty tricks" such as saving and reloading the db in a given state, my tests are oblivious to schema changes and implementation changes.由于我不使用“肮脏的技巧”,例如在给定的 state 中保存和重新加载数据库,因此我的测试忽略了架构更改和实现更改。

Disadvantages : The tests are getting difficult to maintain, any change in a test affects other tests.缺点:测试变得难以维护,测试中的任何更改都会影响其他测试。 The tests run 8-9 min which is great for continuous integration but is a bit frustrating for developers.测试运行 8-9 分钟,这对于持续集成非常有用,但对开发人员来说有点令人沮丧。 Tests cannot be run isolated, the best you can do is to stop after the test you are interested in has run - but you absolutely must run all the tests that come before.测试不能单独运行,你能做的最好的就是在你感兴趣的测试运行之后停止——但你绝对必须运行之前的所有测试。

How would you go about improving my tests?你会如何改进我的测试?

First, understand the tests you have are integration tests (probably access external systems and hit a wide range of classes).首先,了解您拥有的测试是集成测试(可能会访问外部系统并涉及广泛的类)。 Unit tests should be a lot more specific, which is a challenge on an already built system.单元测试应该更加具体,这对已经构建的系统来说是一个挑战。 The main issue achieving that is usually the way the code is structured:实现这一目标的主要问题通常是代码的结构方式:

ie class tightly coupled to external systems (or to other classes that are).即 class 与外部系统(或其他类)紧密耦合。 To be able to do so you need to build the classes in such a way that you can actually avoid hitting external systems during the unit tests.为了能够做到这一点,您需要以这样一种方式构建类,即您实际上可以避免在单元测试期间触及外部系统。

Update 1: Read the following, and consider that the resulting design will allow you to actually test the encryption logic without hitting files/databases - http://www.lostechies.com/blogs/gabrielschenker/archive/2009/01/30/the-dependency-inversion-principle.aspx (not in java, but ilustrates the issue very well)... also note that you can do a really focused integration tests for the readers/writers, instead of having to test it all together.更新 1:阅读以下内容,并考虑生成的设计将允许您在不访问文件/数据库的情况下实际测试加密逻辑 - http://www.lostechies.com/blogs/gabrielschenker/archive/2009/01/30/ the-dependency-inversion-principle.aspx (不在 java 中,但很好地说明了这个问题)......还请注意,您可以为读者/作者进行真正集中的集成测试,而不必一起测试。

I suggest:我建议:

  • Gradually include real unit tests on your system.逐渐在您的系统上包含真正的单元测试。 You can do this when doing changes and developing new features, refactoring appropriately.您可以在进行更改和开发新功能时执行此操作,并进行适当的重构。
  • When doing the previous, include focused integration tests where appropriate.在执行上述操作时,在适当的情况下包括重点集成测试。 Make sure you are able to run the unit tests separated from the integration tests.确保您能够运行与集成测试分开的单元测试。
  • Consider your tests are close to testing the system as a whole, thus are different from automated acceptance tests only in that they operate on the border of the API.考虑到您的测试接近于测试整个系统,因此与自动化验收测试的不同之处仅在于它们在 API 的边界上运行。 Given this think about factors related to the importance of the API for the product (like if it will be used externally), and whether you have good coverage with automated acceptance tests.考虑到这一点,请考虑与 API 对产品的重要性相关的因素(例如是否将在外部使用),以及自动化验收测试是否具有良好的覆盖率。 This can help you understand what is the value of having these on your system, and also why they naturally take so long.这可以帮助您了解在您的系统上安装这些的价值是什么,以及为什么它们自然需要这么长时间。 Take a decision on whether you will be testing the system as a whole on the interface level, or both the interface+api level.决定您是在接口级别上作为一个整体测试系统,还是同时在接口+api 级别上进行测试。

Update 2: Based on other answers, I want to clear something regarding doing TDD.更新 2:根据其他答案,我想澄清一些关于做 TDD 的事情。 Lets say you have to check whether some given logic sends an email, logs the info on a file, saves data on the database, and calls a web service (not all at once I know, but you start adding tests for each of those).假设您必须检查某些给定逻辑是否发送 email,将信息记录在文件中,将数据保存在数据库中,并调用 web 服务(我不是一下子就知道,但您开始为每一个添加测试) . On each test you don't want to hit the external systems, what you really want to test is if the logic will make the calls to those systems that you are expecting it to do.在您不想触及外部系统的每个测试中,您真正想要测试的是逻辑是否会调用您期望它执行的那些系统。 So when you write a test that checks that an email is sent when you create an user, what you test is if the logic calls the dependency that does that.因此,当您编写一个测试来检查创建用户时是否发送了 email 时,您测试的是逻辑是否调用了执行该操作的依赖项。 Notice that you can write these tests and the related logic, without actually having to implement the code that sends the email (and then having to access the external system to know what was sent...).请注意,您可以编写这些测试和相关逻辑,而无需实际实现发送 email 的代码(然后必须访问外部系统才能知道发送了什么......)。 This will help you focus on the task at hand and help you get a decoupled system.这将帮助您专注于手头的任务并帮助您获得一个解耦的系统。 It will also make it simple to test what is being sent to those systems.它还将使测试发送到这些系统的内容变得简单。

Now you are testing many things in one method (a violation of One Assertion Per Test).现在你正在用一种方法测试很多东西(违反了每个测试一个断言)。 This is a bad thing, because when any of those things changes, the whole test fails.这是一件坏事,因为当任何这些事情发生变化时,整个测试都会失败。 This leads it to not being immediately obvious why a test failed and what needs to be fixed.这导致无法立即明确测试失败的原因以及需要修复的内容。 Also when you intentionally change the behaviour of the system, you need to change more tests to correspond the changed behaviour (ie the tests are fragile).此外,当您有意更改系统的行为时,您需要更改更多测试以对应更改的行为(即测试是脆弱的)。

To know what kind of tests are good, it helps to read more on 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/要了解哪种测试好,请阅读 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/

To improve the test that you mentioned, I would split it into the following three test classes with these context and test method names:为了改进您提到的测试,我会将其拆分为以下三个具有这些上下文和测试方法名称的测试类:

Creating user accounts创建用户帐户

  • Before a user is created在创建用户之前
    • the user does not exist用户不存在
  • When a user is created创建用户时
    • the user exists用户存在
  • When a user is deleted删除用户时
    • the user does not exist anymore该用户不再存在

Logging in在登录

  • When a user exists当用户存在时
    • the user can login with the right password用户可以使用正确的密码登录
    • the user can not login with a wrong password用户无法使用错误的密码登录
  • When a user does not exist当用户不存在时
    • the user can not login用户无法登录

Sending messages发送消息

  • When a user sends a message当用户发送消息时
    • the message appears in the sender's outbox邮件出现在发件人的发件箱中
    • the message appears in the reciever's inbox邮件出现在收件人的收件箱中
    • the message does not appear in any other message boxes该消息不会出现在任何其他消息框中
  • When a message is deleted删除消息时
    • the message does not anymore exist该消息不再存在

You also need to improve the speed of the tests.您还需要提高测试的速度。 You should have a unit test suite with good coverage, which can run in a couple of seconds.你应该有一个覆盖率很高的单元测试套件,它可以在几秒钟内运行。 If it takes longer than 10-20 seconds to run the tests, then you will hesitate to run them after every change, and you lose some of quick feedback that running the tests gives you.如果运行测试的时间超过 10-20 秒,那么每次更改后你都会犹豫是否运行它们,并且你会失去运行测试给你的一些快速反馈。 (If it talks to the database, it's not a unit test, but a system or integration test, which have their uses, but are not fast enough to be executed continually.) You need to break the dependencies of the classes under test by mocking or stubbing them. (如果它与数据库对话,则不是单元测试,而是系统或集成测试,它们有其用途,但速度不够快,无法持续执行。)您需要打破被测类的依赖关系mocking或存根他们。 Also from your description it appears that your tests are not isolated, but instead the tests depend on the side-effects caused by previous tests - this is a no-no.同样从您的描述来看,您的测试似乎不是孤立的,而是测试取决于先前测试引起的副作用 - 这是一个禁忌。 Good tests are FIRST .好的测试是第一位的。

unit tests should - ideally - be independent, and able to run in any order.理想情况下,单元测试应该是独立的,并且能够以任何顺序运行。 So, I would suggest that you:所以,我建议你:

  • break up your tests to be independent将您的测试分解为独立的
  • consider using an in-memory database as the backend for your tests考虑使用内存数据库作为测试的后端
  • consider wrapping each test or suite in a transaction that is rolled back at the end考虑将每个测试或套件包装在最后回滚的事务中
  • profile the unit tests to see where the time is going, and concentrate on that分析单元测试以查看时间在哪里,并专注于那个

if it takes 8 minutes to create a few users and send a few messages, the performance problem may not be in the tests, rather this may be a symptom of performance problems with the system itself - only your profiler knows for sure!如果创建几个用户并发送一些消息需要 8 分钟,则性能问题可能不在测试中,而这可能是系统本身性能问题的症状 - 只有您的分析器才能确定!

[caveat: i do NOT consider these kinds of tests to be 'integration tests', though i may be in the minority; [警告:我不认为这些测试是“集成测试”,尽管我可能是少数; i consider these kinds of tests to be unit tests of features , a la TDD]我认为这些类型的测试是功能的单元测试,a la TDD]

Reduce dependencies between tests.减少测试之间的依赖关系。 This can be done by using Mocks.这可以通过使用 Mocks 来完成。 Martin Fowler speaks about it in Mocks aren't stubs , especially why mocking reduces dependencies between tests. Martin Fowler 在Mocks are not stubs中谈到了这一点,特别是为什么 mocking 减少了测试之间的依赖关系。

You can use JExample , an extension of JUnit that allows test methods to have return values that are reused by other tests.您可以使用JExample ,它是 JUnit 的扩展,它允许测试方法具有可被其他测试重用的返回值。 JExample tests run with the normal JUnit plugin in Eclipse, and also work side by side with normal JUnit tests. JExample 测试与 Eclipse 中的普通 JUnit 插件一起运行,并且还与普通 JUnit 测试一起工作。 Thus migration should be no problem.因此迁移应该没有问题。 JExample is used as follows 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
    }

}

Disclaimer, I am among the JExample developers.免责声明,我是 JExample 开发人员之一。

If you use TestNG you can annotate tests in a variety of ways.如果您使用TestNG ,您可以通过多种方式注释测试。 For example, you can annotate your tests above as long-running .例如,您可以将上面的测试注释为long-running Then you can configure your automated-build/continuous integration server to run these, but the standard "interactive" developer build would not (unless they explicitly choose to).然后您可以配置您的自动构建/持续集成服务器来运行这些,但标准的“交互式”开发人员构建不会(除非他们明确选择)。

This approach depends on developers checking into your continuous build on a regular basis, so that the tests do get run!这种方法依赖于开发人员定期检查您的持续构建,以便测试运行!

Some tests will inevitably take a long time to run.有些测试不可避免地需要很长时间才能运行。 The comments in this thread re.此线程中的评论重新。 performance are all valid.表现都是有效的。 However if your tests do take a long time, the pragmatic solution is to run them but not let their time-consuming nature impact the developers to the point that they avoid running them.但是,如果您的测试确实需要很长时间,实用的解决方案是运行它们,但不要让它们耗时的性质影响到开发人员避免运行它们的程度。

Note: you can do something similar with JUnit by (say) naming tests in different fashions and getting your continuous build to run a particular subset of test classes.注意:您可以对 JUnit 做类似的事情,方法是(比如说)以不同的方式命名测试并让您的连续构建运行特定的测试类子集。

By testing stories like you describe, you have very brittle tests.通过测试你描述的故事,你有非常脆弱的测试。 If only one tiny bit of functionality is changing, your whole test might be messed up.如果只有一小部分功能发生变化,您的整个测试可能会一团糟。 Then you will likely to change all tests, which are affected by that change.然后,您可能会更改所有受该更改影响的测试。

In fact the tests you are describing are more like functional tests or component tests than unit tests.实际上,您所描述的测试更像是功能测试或组件测试,而不是单元测试。 So you are using a unit testing framework (junit) for non-unit tests.因此,您正在使用单元测试框架(junit)进行非单元测试。 In my point of view there is nothing wrong to use a unit testing framework to do non-unit tests, if (and only if) you are aware of it.在我看来,使用单元测试框架进行非单元测试并没有错,如果(且仅当)你知道它。

So there are following options:所以有以下选择:

  • Choose another testing framework which supports a "story telling"-style of testing much better , like other user already have suggested.像其他用户已经建议的那样,选择另一个更好地支持“讲故事”式测试的测试框架 You have to evaluate and find a suitable testing framework.您必须评估并找到合适的测试框架。

  • Make your tests more “unit test”-like.让你的测试更像“单元测试”。 Therefore you will need to break up your tests and maybe change your current production code.因此,您将需要分解您的测试,并可能更改您当前的生产代码。 Why?为什么? Because unit testing aims on testing small units of code (unit testing purists suggest only one class at once).因为单元测试旨在测试小代码单元(单元测试纯粹主义者建议一次只使用一个 class)。 By doing this your unit tests become more independent.通过这样做,您的单元测试变得更加独立。 If you change the behavior of one class, you just need to change a relatively small amount of unit test code.如果你改变一个 class 的行为,你只需要改变相对少量的单元测试代码。 This makes your unit test more robust.这使您的单元测试更加健壮。 During that process you might see that your current code does not support unit testing very well -- mostly because of dependencies between classes.在这个过程中,您可能会看到您当前的代码不能很好地支持单元测试——主要是因为类之间的依赖关系。 This is the reason that you will also need to modify your production code.这就是您还需要修改生产代码的原因。

If you are in a project and running out of time, both options might not help you any further.如果您在一个项目中并且时间不多了,那么这两个选项可能对您没有任何帮助。 Then you will have to live with those tests, but you can try to ease your pain:然后你将不得不忍受这些测试,但你可以尝试减轻你的痛苦:

  • Remove code duplication in your tests : Like in production code eliminate code duplication and put the code into helper methods or helper classes.删除测试中的代码重复:就像在生产代码中一样,消除代码重复并将代码放入辅助方法或辅助类中。 If something changes, you might only need to change the helper method or class.如果发生变化,您可能只需要更改辅助方法或 class。 This way you will converge to the next suggestion.这样,您将收敛到下一个建议。

  • Add another layer of indirection to your tests: Produce helper methods and helper classes which operate on a higher level of abstraction.为您的测试添加另一层间接:生成在更高抽象级别上运行的辅助方法和辅助类。 They should act as API for your tests.它们应该充当您的测试的 API。 These helpers are calling you production code.这些助手称您为生产代码。 Your story tests should only call those helpers.你的故事测试应该只调用那些助手。 If something changes, you need to change only one place in your API and don't need to touch all your tests.如果发生变化,您只需更改 API 中的一个位置,并且不需要触及所有测试。

Example signatures for your API: API 的示例签名:

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

For general unit testing I suggest to have a look into XUnit Test Patterns from Gerard Meszaros .对于一般单元测试,我建议查看Gerard Meszaros 的 XUnit 测试模式

For breaking dependencies in your production tests have a look into Working Effectively with Legacy Code from Michael Feathers要在生产测试中打破依赖关系,请查看Michael Feathers 的有效使用遗留代码

In addition to the above, pick up a good book on TDD (I can recommend "TDD and Acceptance TDD for Java Developers").除了以上,再找一本关于TDD的好书(我可以推荐《TDD and Acceptance TDD for Java Developers》)。 Even though it will approach from a TDD point of view there is alot of helpful information about writing the right kind of unit tests.即使它会从 TDD 的角度来看,也有很多关于编写正确类型的单元测试的有用信息。

Find someone who has alot of knowledge in the area and use them to figure out how you can improve your tests.找一个在该领域有很多知识的人,并用他们来弄清楚如何改进你的测试。

Join a mailing list to ask questions and just read the traffic coming through.加入邮件列表提出问题并阅读通过的流量。 The JUnit list at yahoo (something like groups.yahoo.com/junit). yahoo 上的 JUnit 列表(类似于 groups.yahoo.com/junit)。 Some of the movers and shakers in the JUnit world are on that list and actively participate. JUnit 世界中的一些推动者和震动者都在该名单上并积极参与。

Get a list of the golden rules of unit tests and stick them on your (and others) cubicle wall, something like:获取单元测试的黄金法则列表,并将它们粘贴在您(和其他人)的隔间墙上,例如:

  • Thou shalt never access an external system你永远不能访问外部系统
  • Thou shalt only test the code under test你应该只测试被测代码
  • Thou shalt only test one thing at once etc.您一次只能测试一件事等。

Since everyone else is talking about structure I'll pick different points.由于其他人都在谈论结构,我会选择不同的观点。 This sounds like a good opportunity to profile the code to find bottleknecks and to run it through code coverage to see if you are missing anything (given the time it takes to run it the results could be interesting).这听起来像是一个很好的机会来分析代码以查找瓶颈并通过代码覆盖率运行它以查看您是否缺少任何东西(考虑到运行它所花费的时间,结果可能很有趣)。

I personally use the Netbeans profiler, but there are ones in other IDEs and stand alone ones as well.我个人使用 Netbeans 分析器,但在其他 IDE 中也有,也有独立的。

For code coverage I use Cobertura , but EMMA works too (EMMA had an annoyance that Cobertura didn't have... I forget what it was and it may not be an issue anymore).对于代码覆盖率,我使用Cobertura ,但EMMA也有效(EMMA 有一个 Cobertura 没有的烦恼……我忘记了它是什么,它可能不再是问题了)。 Those two are free, there are paid ones as well that are nice.这两个是免费的,也有付费的,很好。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何将这些JUnit 3测试转换为JUnit 4? - How can I convert these JUnit 3 tests to JUnit 4? 如何使用PageObject模式改善测试? - How can I improve my tests using PageObject pattern? 如何为JUnit测试声明“mixins”? - How can I declare “mixins” for JUnit tests? 如果使用Spring和Junit 4,如何编写内部测试类? 还是我还能如何构建测试? - How to write inner test classes if you use Spring and Junit 4? Or How else I can structure my tests? 为什么这个 class 不适合 Junit 测试,我该如何改进它? - why is this class not good for Junit testing and how can i improve it? 如何在不依赖数据库的情况下将JUnit测试应用于我的Insert(),update(),delete()方法(使用Mockito) - How can I apply JUnit tests to my Insert(),update(),delete() methods without database dependency (with Mockito) 如何使用不同的 JVM 参数重新运行 JUnit 测试? - How can I re-run my JUnit tests with different JVM args? 如何根据条件自动跳过某些JUnit测试? - How can I automatically skip certain JUnit tests based on a condition? 我如何成功地结合这两个JUnit测试的逻辑? - How can I successfully combine the logic of these two JUnit tests? 模拟文件路径时如何编写有效的 JUnit 测试? - How can I write valid JUnit tests when mocking a filepath?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM