I wrote some sorting algorithms for a class assignment and I also wrote a few tests to make sure the algorithms were implemented correctly. My tests are only like 10 lines long and there are 3 of them but only 1 line changes between the 3 so there is a lot of repeated code. Is it better to refactor this code into another method that is then called from each test? Wouldn't I then need to write another test to test the refactoring? Some of the variables can even be moved up to the class level. Should testing classes and methods follow the same rules as regular classes/methods?

Right on. Tests are code -- all the same principles for writing good code still apply! Test the refactoring by running the tests, but be sure that there is adequate coverage and that yo're hitting more than one boundary condition in you tests (e.g. a normal condition vs. a failure condition).
–
MichaelMar 18 '12 at 22:47

5

I disagree. Tests don't necessarily have to be DRY, it's more important for them to be DAMP (Descriptive And Meaningful Phrases) than DRY. (In general, at least. In this specific case though, pulling out the repeated initialization into a helper definitely makes sense.)
–
Jörg W MittagMar 19 '12 at 4:21

1

I've never heard DAMP before, but I like that description.
–
Joachim SauerApr 20 '12 at 7:33

@Jörg W Mittag: You can still be DRY and DAMP with tests. I usually refactor the different ARRANGE-ACT-ASSERT (or GIVEN-WHEN-THEN) parts of the test to helper methods in the test fixture if I know that some part of the test repeats. They usually have DAMP names, such as givenThereAreProductsSet(amount) and even as simple as actWith(param). I managed to do it with fluent api wise (e.g. givenThereAre(2).products()) once, but I quickly stopped because it felt like an overkill.
–
SpoikeApr 20 '12 at 8:19

No, it's not OK. You should use a TestDataBuilder instead. You should also take care about readability of your tests : a ? 1000 ? b ? If tomorrow one have to work on the implementation you are testing, tests are a great way to enter the logic : write your tests for you fellow programmers, not for the compiler :)

Even more than production code, test code needs to be optimized for readability and maintainability, as it has to be maintained along the code being tested and also be read as part of the documentation. Consider how copied code may make test code maintenance harder and how that may become an incentive not to write tests for everything. Also, don't forget that when you write a function to DRY your tests, it also should be subject to tests.

Duplicating code for tests is an easy trap to fall into. Sure it's convenient, but what happens if you start to refactor your implementation code and your tests all start needing to change? You run the same risks that you do if you've duplicated your implementation code, in that you will most likely need to change your test code in many places also. This all adds up to a great deal of wasted time, and an increasing number of failure points that need to be dealt with, which means that the cost to maintain your software becomes unnecessarily high, and therefore reduces the overall business value of the software you work on.

Consider also that what is easy to do in tests will become easy to do in the implementation. When you are pressed for time and under a lot of stress, people tend to rely on learned patterns of behavior and generally try and do what seems easiest at the time. So, if you find you cut and paste a lot of your test code, it's likely you'll be doing the same in your implementation code, and this is a habit you want to avoid early on in your career, to save you a lot of difficulty later on when you find yourself having to maintain older code that you have written, and that your company can't necessarily afford to rewrite.

As others have said, you apply the DRY principal, and you look for opportunities to refactor any likely duplications to helper methods and helper classes, and yes, you should even be doing this in your tests in order to maximize the reuse of code and save yourself facing difficulties with maintenance later on. You may even find yourself slowly developing a testing API that you can use over and over again, possibly even in multiple projects - certainly that is how things have happened for me over the last several years.