Testování privátních metod

V některých případech obsahuje třída složité metody, které však používá pouze tato třída a je zbytečné měnit jejich viditelnost jen kvůli otestování. K otestování takových metod je nutné využít reflexi.

Třída, kterou budeme testovat. Tato třída obsahuje veřejnou metodu compute() a privátní metodu getLongFromString(). Obě tyto metody budeme chtít otestovat.

public class MyClass {
    
    public long compute(String num1, String num2) {
          return Math.abs(getLongFromString(num1) - getLongFromString(num2));
    }

    private long getLongFromString(String number) {
          return Long.parseLong(number);
    }
}

Třída, která slouží jako předek všech testovacích tříd. Tato třída obsahuje metodu getPrivateMethods, která na základě třídy, názvu metody a jejich parametrů dokáže vrátit a zpřístupnit danou metodu na daném objektu.

import java.lang.reflect.Method;

public class AbstractTest {
 
       protected Method getPrivateMethod(Class<?> clazz, String methodName, Class<?> ... parameterTypes) {
             try {
                    // get method vrací pouze public metody
                    // Method method = clazz.getMethod(methodName, parameterTypes);
                    // proto je potřeba volat getDeclaredMethod
                    Method method = clazz.getDeclaredMethod(methodName, parameterTypes);
                    method.setAccessible(true);
                    return method;
             } catch (Exception e) {
                    throw new IllegalArgumentException("Cannot obtain method with name: " + methodName, e);
             }
       }
}

A nakonec třída s testy. Metodu getLongFromString testujeme dvakrát. Jednou pro případ, že zadáme správné parametry a podruhé testujeme, že vyhodí výjimku.

import static org.junit.Assert.assertTrue;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
 
import org.junit.BeforeClass;
import org.junit.Test;
 
public class MyClassTest extends AbstractTest {
       private static MyClass myClass;
      
       @BeforeClass
       public static void prepare() {
             myClass = new MyClass();
       }
 
       @Test
       public void computeOK() {
             String n1 = "120";
             String n2 = "330";
            
             long result = myClass.compute(n1, n2);
             assertTrue(result == 210);
       }
      
       @Test
       public void getLongFromStringOK() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
             String n = "30";
            
             Method testedMethod = getPrivateMethod(MyClass.class, "getLongFromString", String.class);
             long result = (Long) testedMethod.invoke(myClass, n);
            
             assertTrue(result == 30);
       }
      
       @Test(expected = InvocationTargetException.class)
       public void getLongFromStringThrowsException() throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
             String s = "AB";
            
             Method testedMethod = getPrivateMethod(MyClass.class, "getLongFromString", String.class);
             testedMethod.invoke(myClass, s);
       }
      
}

Anotace @BeforeClass znamená, že daná metoda se volá jednou a to před voláním jakékoliv jiné metody ve třídě. Anotace @Test(expected = InvocationTargetException.class) definuje, jaký typ výjimky má být vyhozen. Metoda getLongFromString sice vyhodí NumberFormatException, ale ta pak způsobí InvocationTargetException. Z toho důvodu očekáváme při testu tuto výjimku.

Napsat komentář