views:

217

answers:

2

Developers have many different options out there to create fast and relatively maintainable unit test suites. But this takes a great deal of knowledge involved in decoupling modules of code, isolating the code under test within its test context, and using test doubles (stubs, fakes, mocks). What's more confusing is that within the concept of doubling itself, there are many different approaches that are vastly different from one another:

  • Using dynamic languages/metaprogramming or instrumentation approaches to override implementations of methods (even static methods or initializers) for the duration of a test run.
  • Interaction based approaches based on defining expectations and recording expected calls on doubles.
  • Using "fake" double implementations provided for testing with a third-party framework you're using.
  • Creating your own stubs.

This means while someone may be familiar with the "mock" objects (I prefer to call these fakes) provided by SpringMVC, for instance, some interaction-based mocking code can leave the same person scratching their head. (Is this what happened to Hitler's developers?)

My experience has been (and I suspect others have seen this, too) that very few developers are familiar with this requisite knowledge, and even fewer have enough expertise to develop suites that are somewhat maintainable. (Let's face it, maintainability is a very relative term when we're talking about tests!) Furthermore, often I'll see developers break a test they aren't familiar with and their first instinct is to comment it out rather than fix the test or the code that is broken. Time is often lost in the long-run, but it seems less painful initially.

On your team, how have you dealt with educating and addressing maintenance problems that come about when we don't understand each others' test code?

+2  A: 

I have found the following have helped:

  • Create and enforce naming conventions for tests.
  • Follow the Arrange, Act, Assert pattern, no matter what.
  • Consider setup methods a very strong code-smell in tests.
  • Create good examples and exercises for each doubling technique.
  • Post visual feedback of test metrics where other team members can see.
cwash
I'm curious as to why you consider setup methods to be a code smell. An indicator that the class under test is hard to instantiate?
Mark Roddy
It makes the tests harder to read (say, look at quickly when broken and figure out how to fix). They have their place, but I think tests don't have to be perfectly DRY. It's OK if they contain some duplication (and it's OK if they have to use a setup) as long as they are focused and the team understands them.
cwash
@Mark - to clarify, it's a code-smell for the test code. The bigger the setup, the smellier the test. Also, the more you have in your setup, the bigger your test context is. The bigger your test context is, the less maintainable your test will be.
cwash
See this link for more info: http://jamesnewkirk.typepad.com/posts/2007/09/why-you-should-.html
cwash
@cwash I totally agree that in the example given in that blog post that the setUp method was causing more harm then good and should be removed. However, this doesn't necessarily translate to the setUp method should never be used or out right removed (the blog author's words, not anyone's here).
Mark Roddy
I think there's also two distinctions I'd like to be made:1) In the example, the MoneyBag class's behavior is highly dependent on values it is created with, so testing against different values will be required which would make setUp bloated if it was responsible for object creation. For classes whose behavior are not as dependent on user supplied values this is not necessarily the case.2) Best practices seem to vary between dynamic and static languages. I've had disagreements with people before where in the end turned out we were both right for the context we work in.
Mark Roddy
@Mark I agree with what you're saying, I don't agree with how he phrases it either, that's why I call it a code smell. It's acceptable to use them, especially when I have a good reason. I'm going off Fowler's definition of a code smell: http://martinfowler.com/bliki/CodeSmell.html, i.e. "[S]mells don't always indicate a problem... You have to look deeper to see if there is an underlying problem there - smells aren't inherently bad on their own - they are often an indicator of a problem rather than the problem themselves."
cwash
In a test class, its the same class being tested in all tests (should be, anyway) - this normally means that the construction / initialization code is copied and pasted in every test. A setUp helps greatly in avoiding that. If you happen to be on Java/GWT there are disarming methods and restoring methods you need to call before and after each test to ensure that correct test environment is used... thats another great use for a setUp and tearDown.
Sudhir Jonathan
Jay Fields has argued against setup/teardown extensively where it greatly reduces or inhibits readability (http://architects.dzone.com/news/testing-duplicate-code-your-te). In practice, I think it dogmatic to take it as far as Jay, but he does have a valid point. The most important part about your tests (for maintainability) is that they are focused and readable. To the extent that setup and teardown takeaway from this, you should consider them a code smell... that's all I mean.
cwash
+5  A: 

I work in a 15 member team in a company that's anal about Test Driven Development, so we come across this problem fairly often. The reason I say fairly often rather than 'every five minutes' is because we try to follow these practices:

  1. Pair programming and pair rotation: although only a few members at the beginning have the know how and experience for TDD, pairing very quickly spreads the knowledge around. We have freshers who come in, work with the people for one week, and start lecturing on testing strategies. This also involves rotating the pairs, so everyone works with everyone else regularly.

  2. Keep great test naming conventions: We try to name our tests very expressively. Examples include public void testThatSavingTheOrderDispatchesItBeforeWritingToTheDB(). While it may seem funny, this makes for very productive testing, especially when coupled with sensible and readable method and variable names.

  3. We also read up on the latest mock and testing frameworks available. Tools like Mocha / RSpec (Ruby) and JUnit / Mockito (Java) are very powerful and can be learned in a short while. There is a learning curve, but its well worth the effort.

  4. Once you choose a testing and mocking framework, keep only that framework. The top few frameworks in any language are usually only different in terms of syntax, not power. So there isn't any reason to pollute the code with more than one way to do the same thing.

  5. Nothing beats training, self motivation, and working closely with those who already know. I've learnt the most testing in one hour working with a fellow programmer who worked with a senior programmer for two weeks :D

Sudhir Jonathan
I'm sure how much pair programming will be accepted in other organizations, but at least try to keep communication / mentorship / training levels high.
Sudhir Jonathan
Good point regarding pairing. I'd love to do this but the client won't officially allow it. We do it informally and at the very least try to review each others' code.
cwash
True, too many people don't see the advantages in pair programming... but yeah, it has big advantages whether or not you're trying to work in an agile environment. Both parties wind up writing better code :)
Sudhir Jonathan