Как издеваться над строкой с помощью mockito?

Мне нужно смоделировать тестовый сценарий, в котором я вызываю метод getBytes() объекта String и получаю исключение UnsupportedEncodingException.

Я попытался добиться этого, используя следующий код:

String nonEncodedString = mock(String.class);
when(nonEncodedString.getBytes(anyString())).thenThrow(new UnsupportedEncodingException("Parsing error."));

Проблема в том, что когда я запускаю свой тестовый пример, я получаю MockitoException, в котором говорится, что я не могу имитировать класс java.lang.String.

Есть ли способ имитировать объект String с помощью mockito или, в качестве альтернативы, способ заставить мой объект String генерировать исключение UnsupportedEncodingException, когда я вызываю метод getBytes?


Вот более подробные сведения, иллюстрирующие проблему:

Это класс, который я хочу протестировать:

public final class A {
    public static String f(String str){
        try {
            return new String(str.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            // This is the catch block that I want to exercise.
            ...
        }
    }
}

Это мой тестовый класс (я использую JUnit 4 и mockito):

public class TestA {

    @Test(expected=UnsupportedEncodingException.class)
    public void test(){
        String aString = mock(String.class);
        when(nonEncodedString.getBytes(anyString())).thenThrow(new UnsupportedEncodingException("Parsing error."));
        A.f(aString);
    }
}

person Alceu Costa    schedule 03.07.2009    source источник
comment
Требование проекта заключается в том, что процент покрытия модульными тестами должен быть выше заданного значения. Для достижения такого процента покрытия тесты должны охватывать блок catch относительно UnsupportedEncodingException.   -  person Alceu Costa    schedule 03.07.2009
comment
Пора подвергнуть сомнению требования к проекту, если вы спросите меня. Покрытие тестирования следует использовать с умом, как и все метрики.   -  person Nick Holt    schedule 03.07.2009
comment
+1, чтобы отменить голос Даффимо. то, что вы не видите причин для этого, не означает, что у OP нет причины.   -  person Peter Recore    schedule 28.06.2010


Ответы (15)


Проблема в том, что класс String в Java отмечен как final, поэтому вы не можете имитировать использование традиционных фреймворков для имитации. Согласно часто задаваемым вопросам по Mockito, это также ограничение этой структуры.

person Peter    schedule 03.07.2009
comment
С Mockito 2 вы можете имитировать финальные классы. - person Josh M.; 17.07.2018

Как насчет того, чтобы просто создать String с неправильным именем кодировки? Видеть

public String(byte bytes[], int offset, int length, String charsetName)

Издевательство над String почти наверняка плохая идея.

person Steve Freeman    schedule 05.09.2009

Если все, что вы собираетесь делать в своем блоке catch, - это генерировать исключение во время выполнения, вы можете сэкономить время на вводе текста, просто используя объект Charset для указания имени вашего набора символов.

public final class A{
    public static String f(String str){
        return new String(str.getBytes(Charset.forName("UTF-8")));
    }
}

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

person Martin Hilton    schedule 21.05.2010
comment
+1 Определенно лучший ответ (кроме случаев использования JDK старше 1.6 - но это маловероятно в 2016 году, поскольку getBytes(Charset) был добавлен только в Java 6). - person Rogério; 20.05.2016

Как указывали другие, вы не можете использовать Mockito для издевательства над последним классом. Однако более важным моментом является то, что тест не особенно полезен, потому что он просто демонстрирует, что String.getBytes() может вызвать исключение, что он, очевидно, может сделать. Если вы серьезно относитесь к тестированию этой функции, я думаю, вы могли бы добавить параметр для кодировки в f() и отправить неверное значение в тест.

Кроме того, вы вызываете ту же проблему для вызывающего A.f(), потому что A является окончательным, а f() - статическим.

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

person Jason    schedule 03.12.2009

Из своей документации JDave не может удалить модификаторы final из классов, загруженных загрузчиком классов начальной загрузки. Сюда входят все классы JRE (из java.lang, java.util и т. Д.).

Инструмент, который позволяет имитировать что угодно, - это JMockit.

С JMockit ваш тест может быть записан как:

import java.io.*;
import org.junit.*;
import mockit.*;

public final class ATest
{
   @Test(expected = UnsupportedOperationException.class)
   public void test() throws Exception
   {
      new Expectations()
      {
         @Mocked("getBytes")
         String aString;

         {
            aString.getBytes(anyString);
            result = new UnsupportedEncodingException("Parsing error.");
         }
      };

      A.f("test");
   }
}

при условии, что полный класс «А»:

import java.io.*;

public final class A
{
   public static String f(String str)
   {
      try {
         return new String(str.getBytes("UTF-8"));
      }
      catch (UnsupportedEncodingException e) {
         throw new UnsupportedOperationException(e);
      }
   }
}

Я действительно выполнил этот тест на своей машине. (Обратите внимание, что исходное проверенное исключение я заключил в исключение времени выполнения.)

Я использовал частичное издевательство через @Mocked("getBytes"), чтобы JMockit не издевался над всем в классе java.lang.String (только представьте, к чему это может привести).

В этом тесте действительно нет необходимости, потому что «UTF-8» - это стандартная кодировка, которая должна поддерживаться во всех JRE. Следовательно, в производственной среде блок catch никогда не будет выполнен.

Однако «потребность» или желание прикрыть блокировку улова все еще в силе. Итак, как избавиться от теста, не уменьшая процент покрытия? Вот моя идея: вставить строку с assert false; в качестве первого оператора внутри блока catch и заставить инструмент Code Coverage игнорировать весь блок catch при сообщении о мерах покрытия. Это один из моих «TODO-пунктов» для JMockit Coverage. 8 ^)

person Rogério    schedule 29.07.2009

Мокито не может издеваться над выпускными классами. JMock в сочетании с библиотекой от JDave может. Вот инструкции.

JMock не делает ничего особенного для финальных классов, кроме как полагаться на библиотеку JDave для завершения всего в JVM, поэтому вы можете поэкспериментировать с использованием нефинализатора JDave и посмотреть, будет ли Mockito затем издеваться над ним.

person Yishai    schedule 03.07.2009

Вы также можете использовать расширение PowerMock Mockito для имитации конечных классов / методов даже в системных классах, таких как String. Однако я бы также посоветовал не имитировать getBytes в этом случае и попытаться настроить ваше ожидание так, чтобы вместо этого использовалась настоящая String, заполненная ожидаемыми данными.

person Johan    schedule 21.12.2009
comment
PowerMock не смог имитировать String. - person rapt; 12.03.2016

Вы будете тестировать код, который никогда не может быть выполнен. Поддержка UTF-8 требуется на каждой виртуальной машине Java, см. http://java.sun.com/javase/6/docs/api/java/nio/charset/Charset.html

person Thomas Lötzer    schedule 21.12.2009

Требование проекта заключается в том, что процент покрытия модульными тестами должен быть выше заданного значения. Для достижения такого процента покрытия тесты должны охватывать блок catch относительно UnsupportedEncodingException.

Что это за цель охвата? Некоторые скажут, что съемка для 100% покрытия не всегда хорошая идея.

Кроме того, это не способ проверить, была ли задействована блокировка catch. Правильный способ - написать метод, который вызывает возникновение исключения, и сделать наблюдение за выбросом исключения критерием успеха. Вы делаете это с помощью аннотации JUnit @Test, добавляя «ожидаемое» значение:

@Test(expected=IndexOutOfBoundsException.class) public void outOfBounds() {
   new ArrayList<Object>().get(1);
}
person duffymo    schedule 03.07.2009
comment
Покрытие кода для этого проекта составляет 95%. Я не тот, кто принял решение ... :-) И вы можете проверить, был ли задействован блок catch, используя JUnit + EMMA. - person Alceu Costa; 03.07.2009
comment
Да, но если вы напишете этот тест JUnit, чтобы показать, что исключение выбрано в надлежащих условиях, у вас будет лучший набор тестов, независимо от того, что вам скажет Эмма. Дело в хороших тестах перед покрытием. - person duffymo; 03.07.2009

Вы пробовали передать недопустимое имя charsetName в getBytes (String)?

Вы можете реализовать вспомогательный метод для получения charsetName и переопределить этот метод в своем тесте на бессмысленное значение.

person Rich Seller    schedule 03.07.2009
comment
Проблема в том, что мне пришлось бы изменить класс, который я тестирую ... но это может быть решением. Я мог бы создать установщик для атрибута, используемого для выбора кодировки, просто для целей тестирования. - person Alceu Costa; 03.07.2009

Возможно, вместо A.f (String) следует использовать A.f (CharSequence). Вы можете издеваться над CharSequence.

person Chris Martin    schedule 01.08.2010
comment
CharSequence не имеет getBytes. Вам нужно будет издеваться над toString () из CharSequence, чтобы вернуть фиктивную строку для имитации getBytes (Charset) в этой фиктивной String. И первоначальная проблема возвращается. - person Guillaume Perrot; 03.02.2017

Если вы можете использовать JMockit, посмотрите ответ Роджерио.

Если и только если ваша цель - получить покрытие кода, но не фактически имитировать то, как отсутствующий UTF-8 будет выглядеть во время выполнения, вы можете сделать следующее (и что вы не можете или не хотите использовать JMockit):

public static String f(String str){
    return f(str, "UTF-8");
}

// package private for example
static String f(String str, String charsetName){
    try {
        return new String(str.getBytes(charsetName));
    } catch (UnsupportedEncodingException e) {
        throw new IllegalArgumentException("Unsupported encoding: " + charsetName, e);
    }
}

public class TestA {

    @Test(expected=IllegalArgumentException.class)
    public void testInvalid(){
        A.f(str, "This is not the encoding you are looking for!");
    }

    @Test
    public void testNormal(){
        // TODO do the normal tests with the method taking only 1 parameter
    }
}
person Guillaume Perrot    schedule 03.02.2017

Вы можете изменить свой метод, чтобы использовать интерфейс CharSequence:

public final class A {
    public static String f(CharSequence str){
        try {
            return new String(str.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            // This is the catch block that I want to exercise.
            ...
        }
    }
}

Таким образом, вы все еще можете передать String, но вы можете издеваться как хотите.

person tkruse    schedule 12.01.2018

вместо насмешливой строки введите строковое значение в методе настройки, как показано ниже

public void setup() throws IllegalAccessException {
    FieldUtils.writeField(yourTestClass, "stringVariableName", "value", true);
}
person Jayen Chondigara    schedule 02.02.2021

Если у вас есть блок кода, который никогда не может быть запущен, и требование руководства о 100% тестовом покрытии, то что-то нужно изменить.

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

person Dawood ibn Kareem    schedule 17.12.2011