Каковы ключевые моменты для написания хороших юнит-тестов с макетами - PullRequest
0 голосов
/ 09 марта 2020

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

Я написание моего UT для кода в Java с JUnit и EasyMock, потому что некоторые объекты, вызываемые и используемые в функциях, имеют большой уровень сложности.

Вот мои проблемы:

  • У меня такое ощущение, что мой тест - это просто копия функций, которые я тестирую, в основном потому, что я использую макеты, которые необходимо настроить в соответствии с алгоритмами
  • Мои тесты довольно повторяющиеся потому что, когда я начал работать над тестами, я не учитывал части настройки mocks, которые я использовал в нескольких тестах, но также потому, что я настраивал различные параметры и mocks для каждого теста моего UT (например, Я тестирую функцию foo (A, B, C), и я нашел 5 тестовых примеров, которые интересны для этой функции, мне нужно настроить 15 mocks)

I ' м а Учитывая, что факторизация и повторное использование макетов для каждого теста UT являются хорошим решением для решения этих проблем, и я написал некоторые из них с помощью этих методологий, но я чувствую, что это приводит к снижению в надежности и удобочитаемости UT.

Есть ли у вас какие-либо l aws или советы, которые вы пытаетесь учитывать при написании UT, которые могут оказаться эффективными для решения этих проблем.


Редактировать

Для пояснения приведем несколько примеров кода:

public class functionsToTest{

   public static int getProperQuantity(Object A){

      int returnValue = 0;

      if(A != null && A.getQuantity() != null && A.getQuantity() > 0){
          returnValue = A.getQuantity();
      }

      return returnValue;

   }

}

Вот класс, который я хочу проверить с помощью UT. Объект A является сложным объектом / объектом, созданным с использованием данных из БД, поэтому я должен смоделировать его, чтобы написать свой UT. Я должен написать мои тесты UT:

1 / Создать макеты и настроить их для каждого теста

public void myTest(){
   int expectedValue = 0;
   int output = 0;

   /** Setting up the mocks **/
   // Case 1 : A is null

   // Case 2 : A.getQuantity() is null
   Object A_case2 = createMock(A.class);
   EasyMock.expect(A_case2.getQuantity()).andReturn(null).anyTimes();

   // Case 3 : A.getQuantity() is negative
   Object A_case3 = createMock(A.class);
   EasyMock.expect(A_case3.getQuantity()).andReturn(-1).anyTimes();

   // Case 4 : nominal case
   Object A_case4 = createMock(A.class);
   EasyMock.expect(A_case4.getQuantity()).andReturn(5).anyTimes();

   // End Set Up
   EasyMock.replay(A_case2, A_case3, A_case4);

   /** Executing the tests **/
   // Case 1 : A is null
   expectedValue = 0;
   output = functionsToTest.getProperQuantity(null);
   assertEquals(expectedValue, output);

   // Case 2 : A.getQuantity() is null
   expectedValue = 0;
   output = functionsToTest.getProperQuantity(A_case2);
   assertEquals(expectedValue, output);

   // Case 3 : A.getQuantity() is negative
   expectedValue = 0;
   output = functionsToTest.getProperQuantity(A_case3);
   assertEquals(expectedValue, output);

   // Case 4 : nominal case
   expectedValue = 5;
   output = functionsToTest.getProperQuantity(A_case4);
   assertEquals(expectedValue, output);

}

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

2 / Создать набор макетов для всего модульного теста

public void myTest(){
   int expectedValue = 0;
   int output = 0;

   /** Setting up the mocks **/
   Object A_default = createMock(A.class);

   // Case 1 : A is null

   // Case 2 : A.getQuantity() is null
   EasyMock.expect(A_default.getQuantity()).andReturn(null).times(1);

   // Case 3 : A.getQuantity() is negative
   EasyMock.expect(A_default.getQuantity()).andReturn(-1).times(2);

   // Case 4 : nominal case
   EasyMock.expect(A_default.getQuantity()).andReturn(5).times(3);

   // End Set Up
   EasyMock.replay(A_default);

   /** Executing the tests **/
   // Case 1 : A is null
   expectedValue = 0;
   output = functionsToTest.getProperQuantity(null);
   assertEquals(expectedValue, output);

   // Case 2 : A.getQuantity() is null
   expectedValue = 0;
   output = functionsToTest.getProperQuantity(A_default);
   assertEquals(expectedValue, output);

   // Case 3 : A.getQuantity() is negative
   expectedValue = 0;
   output = functionsToTest.getProperQuantity(A_default);
   assertEquals(expectedValue, output);

   // Case 4 : nominal case
   expectedValue = 5;
   output = functionsToTest.getProperQuantity(A_default);
   assertEquals(expectedValue, output);

}

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

1 Ответ

2 голосов
/ 09 марта 2020

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

Во-вторых, проблема, с которой вы сталкиваетесь при создании множества макетов для запуска одного Тест, как правило, запах кода. Ваш дизайн не подлежит тестированию, и единственное решение - изменить программное обеспечение. Например, если вам нужно создать 15 экземпляров для одного вызова функции в классе, тогда этот класс имеет так много зависимостей, и это плохой дизайн. Возможно, ваш класс знает / делает слишком много и нарушает Правило одиночной ответственности .

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

Существует практика под названием TDD, так что вы можете провести свой дизайн через тесты Таким образом, ваш код всегда тестируемый. Однако самое важное, когда вы разрабатываете свое приложение, вы всегда должны быть осторожны с зависимостями и интерфейсами publi c, потому что это то, что вы собираетесь протестировать позже.

...