Вопрос: Как проверить частную функцию или класс с частными методами, полями или внутренними классами?


Как выполнить модульный тест (используя xUnit) класс с внутренними частными методами, полями или вложенными классами? Или функцию, которая закрывается, если внутренняя связь ( staticв C / C ++) или находится в частной ( анонимный ) пространство имен?

Мне кажется, что изменить модификатор доступа для метода или функции можно просто, чтобы запустить тест.


2234


источник


Ответы:


Если у вас есть какое-то наследие Ява приложения, и вам не разрешено изменять видимость ваших методов, лучший способ проверить частные методы - использовать отражение ,

Внутри мы используем помощников для получения / установки privateа также private staticпеременные, а также вызывать privateа также private staticметоды. Следующие шаблоны позволят вам сделать что угодно, что связано с частными методами и полями. Конечно, вы не можете изменить private static finalпеременные через отражение.

Method method = targetClass.getDeclaredMethod(methodName, argClasses);
method.setAccessible(true);
return method.invoke(targetObject, argObjects);

И для полей:

Field field = targetClass.getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);

Заметки:
1. targetClass.getDeclaredMethod(methodName, argClasses)позволяет заглянуть в privateметоды. То же самое относится и к getDeclaredField,
2. setAccessible(true)требуется играть с рядовыми.


1398



Лучший способ проверить частный метод - через другой общедоступный метод. Если этого не сделать, то выполняется одно из следующих условий:

  1. Частный метод - это мертвый код
  2. Существует запах дизайна рядом с классом, который вы тестируете
  3. Метод, который вы пытаетесь проверить, не должен быть приватным

495



Когда у меня есть частные методы в классе, которые достаточно сложны, что я чувствую необходимость непосредственного тестирования частных методов, это запах кода: мой класс слишком сложный.

Мой обычный подход к решению таких проблем - это разыгрывать новый класс, содержащий интересные биты. Часто этот метод и поля, с которыми он взаимодействует, и, возможно, другой метод или два могут быть извлечены в новый класс.

Новый класс предоставляет эти методы как «общедоступные», поэтому они доступны для модульного тестирования. Новые и старые классы теперь и более простые, чем оригинальный класс, что отлично для меня (мне нужно, чтобы все было просто, или я заблудился!).

Заметьте, что я не предлагаю, чтобы люди создавали классы, не используя свой мозг! Здесь нужно использовать силы модульного тестирования, чтобы помочь вам найти хорошие новые классы.


261



I have used reflection to do this for Java in the past, and in my opinion it was a big mistake.

Strictly speaking, you should not be writing unit tests that directly test private methods. What you should be testing is the public contract that the class has with other objects; you should never directly test an object's internals. If another developer wants to make a small internal change to the class, which doesn't affect the classes public contract, he/she then has to modify your reflection based test to ensure that it works. If you do this repeatedly throughout a project, unit tests then stop being a useful measurement of code health, and start to become a hindrance to development, and an annoyance to the development team.

What I recommend doing instead is using a code coverage tool such as Cobertura, to ensure that the unit tests you write provide decent coverage of the code in private methods. That way, you indirectly test what the private methods are doing, and maintain a higher level of agility.


221



From this article: Testing Private Methods with JUnit and SuiteRunner (Bill Venners), you basically have 4 options:

  • Don't test private methods.
  • Give the methods package access.
  • Use a nested test class.
  • Use reflection.

184



Generally a unit test is intended to exercise the public interface of a class or unit. Therefore, private methods are implementation detail that you would not expect to test explicitly.


94



Just two examples of where I would want to test a private method:

  1. Decryption routines - I would not want to make them visible to anyone to see just for the sake of testing, else anyone can use them to decrypt. But they are intrinsic to the code, complicated, and need to always work (the obvious exception is reflection which can be used to view even private methods in most cases, when SecurityManager is not configured to prevent this).
  2. Creating an SDK for community consumption. Here public takes on a wholly different meaning, since this is code that the whole world may see (not just internal to my application). I put code into private methods if I don't want the SDK users to see it - I don't see this as code smell, merely as how SDK programming works. But of course I still need to test my private methods, and they are where the functionality of my SDK actually lives.

I understand the idea of only testing the "contract". But I don't see one can advocate actually not testing code - your mileage may vary.

So my tradeoff involves complicating the JUnits with reflection, rather than compromising my security & SDK.


55



The private methods are called by a public method, so the inputs to your public methods should also test private methods that are called by those public methods. When a public method fails, then that could be a failure in the private method.


51



Another approach I have used is to change a private method to package private or protected then complement it with the @VisibleForTesting annotation of the Google Guava library.

This will tell anybody using this method to take caution and not access it directly even in a package. Also a test class need not be in same package physically, but in the same package under the test folder.

For example, if a method to be tested is in src/main/java/mypackage/MyClass.java then your test call should be placed in src/test/java/mypackage/MyClassTest.java. That way, you got access to the test method in your test class.


26



To test legacy code with large and quirky classes, it is often very helpful to be able to test the one private (or public) method I'm writing right now.

I use the junitx.util.PrivateAccessor-package for Java . Lots of helpful one-liners for accessing private methods and private fields.

import junitx.util.PrivateAccessor;

PrivateAccessor.setField(myObjectReference, "myCrucialButHardToReachPrivateField", myNewValue);
PrivateAccessor.invoke(myObjectReference, "privateMethodName", java.lang.Class[] parameterTypes, java.lang.Object[] args);

26



Having tried Cem Catikkas' solution using reflection for Java, I'd have to say his was a more elegant solution than I have described here. However, if you're looking for an alternative to using reflection, and have access to the source you're testing, this will still be an option.

There is possible merit in testing private methods of a class, particularly with test-driven development, where you would like to design small tests before you write any code.

Creating a test with access to private members and methods can test areas of code which are difficult to target specifically with access only to public methods. If a public method has several steps involved, it can consist of several private methods, which can then be tested individually.

Advantages:

  • Can test to a finer granularity

Disadvantages:

  • Test code must reside in the same file as source code, which can be more difficult to maintain
  • Similarly with .class output files, they must remain within the same package as declared in source code

However, if continuous testing requires this method, it may be a signal that the private methods should be extracted, which could be tested in the traditional, public way.

Here is a convoluted example of how this would work:

// Import statements and package declarations

public class ClassToTest
{
    private int decrement(int toDecrement) {
        toDecrement--;
        return toDecrement;
    }

    // Constructor and the rest of the class

    public static class StaticInnerTest extends TestCase
    {
        public StaticInnerTest(){
            super();
        }

        public void testDecrement(){
            int number = 10;
            ClassToTest toTest= new ClassToTest();
            int decremented = toTest.decrement(number);
            assertEquals(9, decremented);
        }

        public static void main(String[] args) {
            junit.textui.TestRunner.run(StaticInnerTest.class);
        }
    }
}

The inner class would be compiled to ClassToTest$StaticInnerTest.

See also: Java Tip 106: Static inner classes for fun and profit


21