How can I improve my junit tests

后端 未结 9 772
南笙
南笙 2020-12-05 08:59

Right my junit tests look like a long story:

  • I create 4 users
  • I delete 1 user
  • I try to login with the deleted user and make sure it fails
相关标签:
9条回答
  • 2020-12-05 09:00

    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.

    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). Those two are free, there are paid ones as well that are nice.

    0 讨论(0)
  • 2020-12-05 09:01

    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 (i.e. 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/

    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. (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. 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.

    0 讨论(0)
  • 2020-12-05 09:02

    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. 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). 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. 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. 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. 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.

    Example signatures for your 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.

    For breaking dependencies in your production tests have a look into Working Effectively with Legacy Code from Michael Feathers

    0 讨论(0)
  • 2020-12-05 09:10

    In addition to the above, pick up a good book on TDD (I can recommend "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.

    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). Some of the movers and shakers in the JUnit world are on that list and actively participate.

    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.
    0 讨论(0)
  • 2020-12-05 09:13

    Reduce dependencies between tests. This can be done by using Mocks. Martin Fowler speaks about it in Mocks aren't stubs, especially why mocking reduces dependencies between tests.

    0 讨论(0)
  • 2020-12-05 09:23

    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:

    i.e. class tightly coupled to external systems (or to other classes that are). 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.

    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. 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. 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.

    Update 2: Based on other answers, I want to clear something regarding doing 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). 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. 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 ...). 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.

    0 讨论(0)
提交回复
热议问题