Sam
Sam

Reputation: 173

Many Test classes or one Test class with many methods?

I have a PersonDao that I'm writing unit tests against.

There are about 18-20 methods in PersonDao of the form -

    getAllPersons() 
    getAllPersonsByCategory()
    getAllPersonsUnder21() etc

My Approach to testing this was to create a PersonDaoTest with about 18 test methods testing each of the method in PersonDao

Then I created a PersonDaoPaginationTest that tested these 18 methods by applying pagination parameters.

Is this in anyway against the TDD best practices? I was told that this creates confusion and is against the best practices since this is non-standard. What was suggested is merging the two classes into PersonDaoTest instead.

As I understand is, the more broken down into many classes your code is, the better, please comment.

Upvotes: 17

Views: 13479

Answers (5)

Damian Jose
Damian Jose

Reputation: 71

I'm working on test automation of a web app using selenium. It is not unit testing but you might find that some principles apply. Tests are very complex and we figured out that the only way to implement tests in a way that meets all our requirements was having 1 test per class. So we consider that each class is an individual test, then, we were able to use methods as the different steps of the test. For example:

public SignUpTest()
{
   public SignUpTest(Map<String,Object> data){}
   public void step_openSignUpPage(){}
   public void step_fillForm(){}
   public void step_submitForm(){}
   public void step_verifySignUpWasSuccessfull(){}
}

All the steps are dependent, they follow the order specified and if someone fail the others will not be executed.

Of course, each step is a test by itself, but they all together form the sing up test.

The requirements were something like:

  1. Tests must be data driven, this is, execute the same test in parallel with different inputs.
  2. Tests must run in different browsers in parallel as well. So each test will run "input_size x browsers_count" times in parallel.
  3. Tests will focus in a web workflow, for example, "sign up with valid data" and they will be split into smaller tests units for each step of the workflow. It will make things easier to maintain, and debug (when you have a failure, it will say: SignUpTest.step_fillForm() and you'll know immediately what's wrong).
  4. Tests steps share the same test input and state (for example, the id of the user created). Imagine if you put in the same class steps of different tests, for example:

    public SignUpTest()
    {       
       public void signUpTest_step_openSignUpPage(){}
       public void signUpTest_step_step_fillForm(){}
       public void signUpTest_step_step_submitForm(){}
       public void signUpTest_step_verifySignUpWasSuccessfull(){}
    
       public void signUpNegativeTest_step_openSignUpPage(){}
       public void signUpNegativeTest_step_step_fillFormWithInvalidData(){}
       public void signUpNegativeTest_step_step_submitForm(){}
       public void signUpNegativeTest_step_verifySignUpWasNotSuccessfull(){}
    } 
    

    Then, having in the same class state belonging to the 2 tests will be a mess.

I hope I was clear and you may find this useful. At the end, choosing what will represent your test: if a class or a method is just a decision that I think will depend int: what is the target of a test (in my case, a workflow around a feature), what's easier to implement and maintain, if a test fail how you make the failure more accurate and how you make it easier to debug, what will lead you to more readable code, etc.

Upvotes: 1

avh4
avh4

Reputation: 2655

The fact that you have a set of 18 tests that you are going to have to duplicate to test a new feature is a smell that suggests that your PersonDao class is taking on multiple responsibilities. Specifically, it appears to be responsible both for querying/filter and for pagination. You may want to take a look at whether you can do a bit of design work to extract the pagination functionality into a separate class which could then be tested independently.

But in answer to your question, if you find that you have a class that you want to remain complex, then it's perfectly fine to use multiple test classes as a way of organizing a large number of tests. @Gishu's answer of grouping tests by their setup is a good approach. @Ryan's answer of grouping by "facets" or features is another good approach.

Upvotes: 15

Gishu
Gishu

Reputation: 136613

Can't give you a sweeping answer without looking at the code... except use whatever seems coherent to you and your team.

I've found that grouping tests based on their setup works out nicely in most cases. i.e if 5 tests require the same setup, they usually fit nicely into a test-fixture. if the 6th test requires a different setup (more or less) break it out into a separate test fixture.

This also leads to test-fixtures that are feature-cohesive (i.e. tests grouped on feature), give it a try. I'm not aware of any best practice that says you need to have one test class per production class... in practice I find I have n test classes per production classes, the best practice would be to use good names and keep related tests close (in a named folder).

Upvotes: 11

Bohemian
Bohemian

Reputation: 425013

I think one test class per class is fine - if your implementation has many methods, then your test class will have many methods - big deal.

You may consider a couple of things however:

Your methods seem a bit "overly specific" and could use some abstraction or generalisation, for example instead of getAllPersonsUnder21() consider getAllPersonsUnder(int age)

If there are some more general aspects of your class, consider testing them using some common test code using call backs. For a trivial example to illustrate testing that both getAllPersons() returns multiple hits, do this:

@Test
public void testGetAllPersons() {
    assertMultipleHits(new Callable<List<?>> () {
        public List<?> call() throws Exception {
            return myClass.getAllPersons(); // Your call back is here
        }
    });    
}

public static void assertMultipleHits(Callable<List<?>> methodWrapper) throws Exception {
    assertTrue("failure to get multiple items", methodWrapper.call().size() > 0);
}

This static method can be used by any class to test if "some method" returns multiple hits. You could extends this to do lots of tests over the same callback, for example running it with and without a DB connection up, testing that it behaves correctly in each case.

Upvotes: 1

Ryan Stewart
Ryan Stewart

Reputation: 128829

My 2 cents: when you have a large class like that that has different "facets" to it, like pagination, I find it can often make for more understandable tests to not pack them all into one class. I can't claim to be a TDD guru, but I practice test-first development religiously, so to speak. I don't do it often, but it's not exactly rare, either, that I'll write more than a single test class for a particular class. Many people seem to forget good coding practices like separation of concerns when writing tests, though. I'm not sure why.

Upvotes: 5

Related Questions