Artem Zinnatullin
Artem Zinnatullin

Reputation: 4447

Thinking about unit tests structure

I am thinking about how to write tests for my project. At the moment, tests structure is like this:

RealClass 
{ 
      method1; 
      method2; 
      ...
}

and exactly same test class structure:

TestClass {
   testMethod1; 
   testMethod2; 
   ...
 }

But, I do not like it, because I am putting too much test cases in one test method...

May be I should use structure like this:

TestClass {
   testMethod1Opt1; 
   testMethod1Opt2; 
   ... 
   testMethod2Opt1; 
   ...}

How are you writing Unit tests?

Example of my test code: (Very simple test)

public void testIsAppUser() {
    // My (Artem`s Zinnatullin) uId
    final long artemZinnatullinUId = 172672179;

    try {
        assertTrue(usersApi.isAppUser(artemZinnatullinUId));
    } catch (Exception e) {
        fail(e.getMessage());
    }

    // Pavel`s Durov uId
    final long durovUId = 1;

    try {
        assertFalse(usersApi.isAppUser(durovUId));
    } catch (Exception e) {
        fail(e.getMessage());
    }

    // By default uId == current user`s (who has authorized) uId 
    try {
        assertTrue(usersApi.isAppUser(null));
    } catch (Exception e) {
        fail(e.getMessage());
    }
}

What I am thinking about:

public void testIsAppUser1() {
    // My (Artem`s Zinnatullin) uId
    final long artemZinnatullinUId = 172672179;

    try {
        assertTrue(usersApi.isAppUser(artemZinnatullinUId));
    } catch (Exception e) {
        fail(e.getMessage());
    }
}

public void testIsAppUser2() {
    // Pavel`s Durov uId
    final long durovUId = 1;

    try {
        assertFalse(usersApi.isAppUser(durovUId));
    } catch (Exception e) {
        fail(e.getMessage());
    }
}

public void testIsAppUser3() {
    // By default uId == current user`s (who has authorized) uId
    try {
        assertTrue(usersApi.isAppUser(null));
    } catch (Exception e) {
        fail(e.getMessage());
    }
}

Give me advice please.

Upvotes: 0

Views: 345

Answers (4)

Nate-Wilkins
Nate-Wilkins

Reputation: 5492

Only throw when you have an error that might happen because of an 'exception' don't necassarily throw because you can. The following assumes you are creating your own testing enviorment.

I don't know what your assert methods look like but really they should be the ones throwing if anything. You also don't need a try catch to throw an exception you can do the following:

throw new Exception("msg"); // Or another type of Exception

So implementation:

public void AssertEqual(Object obj1, Object obj2) throws Exception
{
    if (!obj1.equals(obj2))
        throw new Exception("Objects are not equal");
}

Upvotes: 1

ben75
ben75

Reputation: 28706

I won't repeat what's in the other responses. But just adding this:

  • Avoid duplication of code construct in your test classes.
  • Don't hesitate to write explicit failure messages.

Here is something to illustrate what I mean:

public void testIsAppUser1() {
    // My (Artem`s Zinnatullin) uId
    assertAppUser(172672179,true,"artemZinnatullinUId");
}

public void testIsAppUser2() {
    // Pavel`s Durov uId
    assertAppUser(1,false,"Pavel`s Durov");
}

public void testIsAppUser3() {
    // By default uId == current user`s (who has authorized) uId
    assertAppUser(null,true,"current user");
}

private void assertAppUser(Long id, boolean isExpectedAppUser, String userName){
    boolean isAppUser = usersApi.isAppUser(id);
    if(isExpectedAppUser){
        assertTrue("User with id:"+id+"and named:"+userName+" must be an appUser" ,isAppUser);
    }else{
        assertFalse("User with id:"+id+"and named:"+userName+" cannot be an appUser" ,isAppUser);
    }
}
}

Upvotes: 1

Nathan Hughes
Nathan Hughes

Reputation: 96385

Your second way of structuring the tests is a lot better. That way each test method covers a different way for the method to break, so you won't have the case where you fix one bug in a method, then have the test fail a little further down (so that one error prevents you from seeing others). It is a lot more important that the test methods be small and sharply-focused than that the test methods map to the methods of the object being tested.

Also, don't catch exceptions, JUnit will do that for you. Add throws Exception to the signature of each of your test methods. If you want to check that an exception is really thrown, then you can catch it in a test, like:

try {
    objectUnderTest.doSomethingThatShouldThrowFooException();
    fail("should've thrown an exception before getting here");
}
catch (FooException fooEx) {
    // yay. my test passed
}

, but the boilerplate of:

} catch (Exception e) {
    fail(e.getMessage());
}

is unnecessary.

Upvotes: 1

Aaron Digulla
Aaron Digulla

Reputation: 328556

Comments:

  1. Instead of try{} catch(){ fail() } just add throws Exception to the test method. JUnit will automatically fail the test for you and preserve the stack trace. This will make bug fixing much easier.

  2. Create small test methods. That creates a name problem: How to come up with lots of good names? The solution here is to name the test after what it logically tests, not which methods it calls.

    If you want to see what methods are called, use a code coverage tool like JaCoCo.

    So the first test should be called testIsArtemsZinnatullinAppUser(). As a guideline: Whenever you feel like you need a comment to explain what a test does, the test name is wrong. Use whatever you'd write in the comment to create a test name.

The reason why you should have smaller tests is that JUnit stops for the first problem. So if you have 20 tests in one test case and the 3rd fails, 17 tests won't run. But these 17 tests could contain valuable information helping to figure out what is wrong.

If they all succeed, then this is probably a specific problem. If many tests fail, the problem must be in shared code.

Upvotes: 2

Related Questions