Как я могу упростить тестирование методов без побочных эффектов в Java? - PullRequest
1 голос
/ 09 августа 2010

Функции (без побочных эффектов) являются таким фундаментальным строительным блоком, но я не знаю удовлетворительного способа их тестирования в Java.

Я ищу указатели на приемы, которые делаюттестировать их проще.Вот пример того, что я хочу:

public void setUp() {
   myObj = new MyObject(...);
}

// This is sooo 2009 and not what I want to write:
public void testThatSomeInputGivesExpectedOutput () {
   assertEquals(expectedOutput, myObj.myFunction(someInput);
   assertEquals(expectedOtherOutput, myObj.myFunction(someOtherInput);
   // I don't want to repeat/write the following checks to see
   // that myFunction is behaving functionally.
   assertEquals(expectedOutput, myObj.myFunction(someInput);
   assertEquals(expectedOtherOutput, myObj.myFunction(someOtherInput);

}


// The following two tests are more in spirit of what I'd like 
// to write, but they don't test that myFunction is functional:
public void testThatSomeInputGivesExpectedOutput () {
   assertEquals(expectedOutput, myObj.myFunction(someInput);
}

public void testThatSomeOtherInputGivesExpectedOutput () {
   assertEquals(expectedOtherOutput, myObj.myFunction(someOtherInput);
}

Я ищу некоторую аннотацию, которую можно поставить на тест (ы), MyObject или myFunction, чтобы инфраструктура теста автоматически повторяла вызовы myFunction во всех возможныхперестановки для заданных комбинаций ввода / вывода, которые я дал, или некоторое подмножество возможных перестановок, чтобы доказать, что функция является функциональной.

Например, над (только) двумя возможными перестановками:

  • myObj = new MyObject ();
  • myObj.myFunction (someInput);
  • myObj.myFunction (someOtherInput);

и:

  • myObj = new MyObject ();
  • myObj.myFunction (someOtherInput);
  • myObj.myFunction (someInput);

Я должен иметь возможность предоставлять только пары ввода / вывода (someInput, Ожидаемый выход) и (someOtherInput, someOtherOutput) иFramework должен сделать все остальное.

Я не использовал QuickCheck, но, похоже, это не решение проблемы.Это задокументировано как генератор.Я не ищу способ генерировать входные данные для моей функции, а скорее среду, которая позволяет декларативно указывать, какая часть моего объекта не имеет побочных эффектов, и вызывать мою спецификацию ввода / вывода, используя некоторую перестановку, основанную на этом объявлении.

Обновление: я не собираюсь проверять, что в объекте ничего не меняется, функция напоминания является типичным вариантом использования для такого типа тестирования, а мемоизер фактически меняет свое внутреннее состояние.Тем не менее, выходные данные с некоторыми входными данными всегда остаются неизменными.

Ответы [ 8 ]

7 голосов
/ 09 августа 2010

Если вы пытаетесь проверить, что функции не имеют побочных эффектов, тогда вызов со случайными аргументами на самом деле не поможет.То же самое касается случайной последовательности вызовов с известными аргументами.Или псевдослучайный, со случайными или фиксированными семенами.Есть хороший шанс, что (вредный) побочный эффект произойдет только с любой из последовательностей вызовов, выбранных вашим рандомизатором.

Существует также вероятность того, что побочные эффекты на самом деле не будут видны на выходах каких-либо звонков, которые вы делаете ... независимо от того, какие это входы.Эти побочные эффекты могут быть связаны с некоторыми другими связанными объектами, которые вы не думаете исследовать.

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

OTOH, если вы уже * знайте, , что функции не имеют побочных эффектов, внедрение рандомизированных тестов "на всякий случай" - пустая трата времени, ИМО.

3 голосов
/ 09 августа 2010

Я не совсем уверен, что понимаю, о чем вы спрашиваете, но похоже, что Junit Theories (http://junit.sourceforge.net/doc/ReleaseNotes4.4.html#theories) может быть ответом.

1 голос
/ 09 августа 2010

В этом примере вы можете создать карту пар ключ / значение (ввод / вывод) и несколько раз вызвать тестируемый метод со значениями, выбранными из карты. Это не докажет, что метод является функциональным, но увеличит вероятность - что может быть достаточно.

Вот краткий пример такого дополнительного, вероятно, функционального теста:

@Test public probablyFunctionalTestForMethodX() {
   Map<Object, Object> inputOutputMap = initMap(); // this loads the input/output values
   for (int i = 0; i < maxIterations; i++) {
     Map.Entry test = pickAtRandom(inputOutputMap); // this picks a map enty randomly
     assertEquals(test.getValue(), myObj.myFunction(test.getKey());
   }
}

Проблемы с более высокой сложностью могут быть решены на основе шаблона Command: вы можете заключить методы тестирования в объекты команды, добавить объект команды в список, перемешать список и выполнить команды (= встроенные тесты) в соответствии этот список.

0 голосов
/ 09 августа 2010

Я думаю, что вам не хватает термина " Параметризованные тесты ".Однако в jUnit он кажется более утомительным, чем в .Net.В NUnit следующий тест выполняется 6 раз со всеми комбинациями.

[Test]
public void MyTest(
    [Values(1,2,3)] int x,
    [Values("A","B")] string s)
{
    ...
}

Для Java ваши параметры выглядят следующим образом:

  • JUnit поддерживает это сверсия 4. Однако кода много (похоже, jUnit непреклонен в том, что методы тестирования не принимают параметры).Это наименее инвазивный.
  • DDSteps , плагин jUnit.См. это видео , которое берет значения из таблицы с соответствующим названием Excel.Вам также нужно написать класс mapper / fixture, который отображает значения из электронной таблицы в члены класса Fixture, которые затем используются для вызова SUT.
  • Наконец, у вас есть Fit / Fitnesse .Это так же хорошо, как DDSteps, за исключением того факта, что входные данные находятся в форме HTML / Wiki.Вы можете вставить лист Excel в Fitnesse, и он отформатирует его одним нажатием кнопки.Здесь вам тоже нужно написать класс приборов.
0 голосов
/ 09 августа 2010

В junit вы можете написать свой собственный бегун тестов.Этот код не тестируется (я не уверен, будут ли методы, которые получают аргументы, распознаваться как методы тестирования, может быть, требуется еще какая-то настройка бегуна?)Реализация getPermutations ().Например, он может взять данные из некоторого поля List<Object[]>, помеченного пользовательской аннотацией, и произвести все перестановки.

0 голосов
/ 09 августа 2010

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

0 голосов
/ 09 августа 2010

Боюсь, что больше не могу найти ссылку, но в Junit 4 есть несколько функций помощи для генерации тестовых данных.Это как:

public void testData() {
  data = {2, 3, 4};
  data = {3,4,5 };
...
 return data;
}

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

0 голосов
/ 09 августа 2010

Похоже, вы пытаетесь проверить, что вызов определенного метода в классе не изменяет ни одно из его полей.Это несколько странный тестовый пример, но вполне возможно написать четкий тест для него.Для других «побочных эффектов», таких как вызов других внешних методов, это немного сложнее.Вы можете заменить локальные ссылки на тестовые заглушки и убедиться, что они не были вызваны, но вы все равно не поймаете вызовы статических методов таким образом.Тем не менее, проверить, что вы не делаете ничего подобного в своем коде, тривиально, и иногда этого должно быть достаточно.

Вот один из способов проверить, что в вызове нет побочных эффектов:

public void test_MyFunction_hasNoSideEffects() {
   MyClass systemUnderTest = makeMyClass();
   MyClass copyOfOriginalState = systemUnderTest.clone();
   systemUnderTest.myFunction();
   assertEquals(systemUnderTest, copyOfOriginalState); //Test equals() method elsewhere
}

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

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

Тем не менее, я повторю, что это мой совет, чтобы вы сосредоточили свое внимание в другом месте.Хотя вы МОЖЕТЕ доказать, что метод в основном не имеет побочных эффектов, во многих случаях может быть лучше быстро проверить это путем визуального осмотра и сосредоточить оставшуюся часть времени на других, более базовых тестах.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...