У меня мало времени, я должен написать один тест!

Java EE
У меня мало времени, я должен написать один тест!

1. Введение

В последнее время я разрабатываю проект, и итеративно выпускаю версию каждые две недели.Это все нормально, но есть особое требование к качеству проекта.Покрытие юнит-тестами фонового кода должно достигать 80%, и этот показатель используется как жесткость запуска. В то время я думал, что это требование было довольно забавным: вам все еще нужно написать один тест для интернет-разработки? ! Только после того, как я в частном порядке проконсультировался с моими партнерами по бизнесу xx nail, я понял, что они уже какое-то время внедряют здесь такие стандарты исследований и разработок, иЭто действительно улучшило качество исследований и разработок, гарантировало качество кода и обслуживания в фоновом режиме и уменьшило задержку доставки из-за проблем с качеством (на самом деле, я полностью согласен). Просто используйте эту возможность, чтобы разобраться в содержании знаний, связанных с модульным тестированием в целом.

2. Знание модульного тестирования

2.1 Модульное тестирование

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

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

  • Статический анализ: изучая код проекта, чтобы найти ошибку или собрать некоторые метрики, обычно используются CheckStyle/FindBugs и другие инструменты.
  • Динамический анализ: предоставляет информацию о трассировках выполнения, временном анализе и покрытии путем наблюдения за действиями программного обеспечения во время его работы.

В сегодняшнюю эпоху победы за счет быстрого выполнения проектов быстрее означает возможность воспользоваться рыночными возможностями, поэтому многие мелкие партнеры будут озадачены: если функции нельзя написать, зачем писать модульные тесты? На самом деле, преимущества написания модульных тестов перевешивают недостатки:

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

В настоящее время модульное тестирование можно разделить на тестирование классов, функциональное тестирование и тестирование интерфейса Соотношение между размером выгод (улучшение качества кода проекта) таково: тестирование классов

                   

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

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

2.2 Покрытие кода

**Покрытие кода — это метод косвенного измерения качества программного обеспечения путем расчета доли исходного кода, выполненного в процессе тестирования, по отношению к общему исходному коду. **Потенциально может обеспечить качество реального продукта при обеспечении качества теста.На основании этого можно найти места в программе, не проверенные тест-кейсами, и в дальнейшем создавать новые тест-кейсы для увеличения охвата . По своей природе оно относится к категории тестирования белого ящика, то есть тестовые примеры в основном разрабатываются в соответствии с внутренней структурой исходного кода, а различные части программного обеспечения тестируются путем проектирования различных входных данных. Распространенные языки программирования имеют соответствующие инструменты тестирования покрытия кода. Анализ на основе покрытия кода может сделать следующее:

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

Многие инструменты разработки, такие как IDEA, имеют встроенные инструменты для статистического охвата.

3. Фреймворк модульного тестирования

3.1 Среда модульного тестирования Java

Большинство фреймворков модульного тестирования включают следующие основные компоненты:

  • TestRuner: Отвечает за выполнение сценариев модульных тестов и отчетность о результатах выполнения тестов;
  • TestFixture: Доступен в виде набора тестовsetUp()а такжеtearDown()метод обеспечения того, чтобы выполнение двух тестовых случаев было независимым друг от друга и не влияло друг на друга;
  • TestResult: этот компонент используется для сбора результатов выполнения каждого TestCase;
  • Test: так какTestSuiteа такжеTestCaseРодительский класс предоставляет метод run() для вызовов TestRunner;
  • TestCase: классы, открытые для пользователей, пользователи могут написать свою собственную логику тестового примера, наследуя TestCase;
  • TestSuite: Обеспечить набор функций для управления testCase.

        JavaВ отрасли существует множество фреймворков модульного тестирования, большинство из которых основано на идеях дизайна фреймворка Junit.Junit4/Junit5/TestNGБольшинство этих фреймворков в настоящее время поддерживают аннотации и параметризованные тесты, указывают разные значения тестов во время выполнения для запуска модульных тестов и работают с инструментами сборки, такими как Maven/Gradle.

3.2 Простой пример Junit

public class JunitDemoTest {

    @Rule
    public Timeout timeout = new Timeout(1000);

    @Before
    public void init() {
        //...
    }

    @After
    public void destroy() {
        //....
    }

    @Test
    public void testAssertArrayEquals() throws InterruptedException {

        byte[] str1 = "test1".getBytes();
        byte[] str2 = "test2".getBytes();
        byte[] str3 = "test".getBytes();

        assertTrue("true", String.valueOf(str1).equals(String.valueOf(str3)));
        assertFalse("false", String.valueOf(str1).equals(String.valueOf(str2)));
        assertArrayEquals("false - byte arrays not same", str1, str2);

        Thread.sleep(50000);
    }
}

3.2 Основной принцип реализации Junit

Когда среда Junit запустится, она будет вызванаJunitCore#run(Runner runner)Методы.

вRunnerЭто абстрактный класс, реализованный по-разному для разных платформ и версий, включаяJunit4ClassRunnerкласс, который наследуетParentRunnerизBlockJUnit4ClassRunnerДобрый/SpringRunnerДобрый/Suitкласс, который вызывает соответствующую реализациюrunметод.

в сбореStatementдеинициализирует правила предварительного запуска и правила предварительного запуска и сопоставления.

вызовchildrenInvokerПосле этого одиночная тестовая задача будет открыта с использованием пула потоков задач. существуетgetFilteredChildrenМетод получит все методы в классе модульного теста, которые необходимо протестировать, и вызовет их циклически.runChildМетод проверит метод и поместит его в пул потоков задач для выполнения.

существуетrunChildпойду сноваmethodBlockГенерируется для каждого метода тестированияStatementобъект,

methodBlockСначала он сгенерирует объект тестового класса, а затем сгенерирует выполнение решения до/после/правила/реального целевого метода, чтобы сформировать цепочку выполнения метода.

RunBefores/RunAftersКлассы наследуютсяStatementкласс, который использует метод цепочки для формирования цепочки выполнения,

использоватьJavaМеханизм отражения вызывает тестовый метод.

существуетrunLeafвызовет оценку сгенерированного объекта оператора, который является началом цепочки выполнения метода. и передать результат вызова черезEachTestNotifierОбъект отправляется, и здесь в основном используется режим слушателя, например, будет отправлена ​​информация о результате операции, сбое или исключении.

Несколько других фреймворков также основаны наJunitРасширение этого базового процесса обеспечивает большое удобство для написания модульных тестов в повседневной разработке.Однако при разработке крупномасштабных проектов также могут возникнуть некоторые проблемы, такие как совместная разработка, например, некоторые зависимые интерфейсы или базовые модули не были учтены. разработанные. , модульные тесты, написанные таким образом, недействительны, и некоторые данные/результаты должны быть «подделаны», чтобы выполнить эти основные зависимости. Чтобы решить эти проблемы,MockПоявилась технология.

4. Мок-фреймворк

4.1 Введение в макет

         MockОбычно означает, что при тестировании объекта S мы создаем некие поддельные объекты для имитации взаимодействия с S, и этиMockПоведение объекта — это то, что мы устанавливаем заранее и ожидаемо. через этиMockОбъект, чтобы проверить, правильно ли работает S в нормальной логике, ненормальной логике или стрессовых ситуациях. представлятьMockСамым большим преимуществом является:MockПоведение фиксировано, оно гарантирует, что при доступе кMockПри вызове метода всегда можно получить ожидаемый результат, который возвращается напрямую без какой-либо логики. Обычно предоставляются некоторые из следующих преимуществ:

  • Изолируйте ошибки в других модулях, чтобы вызвать ошибки тестирования в этом модуле.
  • Изолируйте статус разработки других модулей, пока интерфейс определен, независимо от того, завершена их разработка или нет.
  • Для некоторых более медленных операций вы можете использоватьMock ObjectВместо этого быстро возвращайтесь.
    Для тестирования распределенной системы используйтеMock ObjectЕсть еще два важных преимущества:
  • пройти черезMock ObjectМожно преобразовать некоторые распределенные тесты в локальные тесты.
  • БудуMockИспользуемый для стресс-тестирования, он может решить проблему, заключающуюся в том, что тестовый кластер не может имитировать крупномасштабное давление онлайн-кластера.

Платформа Mock включает EasyMock/JMock/Mokito/PowerMokito, а текущий проект использует PowerMokito.

4.2 Простой фиктивный пример

полныйMock, включая четыре шага постановки целей -> установка условий потребления -> ожидаемые результаты возврата -> потребление и проверка возвращаемых результатов, мы используемMockitoинструменты для реализации простейшего примера,Mockitoглавным образом черезStubДля нагромождения имя и параметры метода используются для точного определения местоположения тестовой заглушки и возврата ожидаемого значения.

@Data
public void TestDao {
    public User getUser(String uid) { return new User("testUser"); }

@Data
public void TestService { 
   private TestDao testDao;
    
    public User getUser(String uid) { return testDao.getUser(uid); }}


@RunWith(PowerMokitoRunner.class)
public void MockDemoTest {
   
  @InjectMock
  private TestService testService

  @Mock
  private TestDao testDao;
  
  @Before
  public void init() {
      Mockito.when(testDao.getUser(any())).thenReturn(new User("test2"));      
  }

  @Test
  public void test1() {
      //调用方法时,会调用到代理对象返回刚才的Mock数据
      User userInfo  = testService.getUser("uid");
  }

}

В основном это соответствует следующим четырем шагам

  • установить цели > пользователь пользователя

  • Установить условия потребления -> testDao.getUser(any())

  • Ожидаемый результат возврата -> thenReturn(..)

  • Потребляйте и проверяйте возвращаемый результат -> testDao.getUser("uid")

4.3 Основной принцип реализации PowerMockito

PowerMockitoОсновной принцип платформы заключается в использовании инструмента байт-кода CGLIGB на основе режима прокси для реализации прокси, который генерирует целевой объект или метод.При использовании, когда вызывать инициализацию, он будет соответствовать, а затем возвращаться в соответствии с возвратом.

PowerMockitoRunner

наследоватьJunit#Runnerкласс, который будет вызываться при запускеrunwithметод, платформа Junit загрузитPowerMokito#PowerMockJUnitRunnerDelegateImplДобрый.

существуетWhitebox#findSingleFieldUsingStrategyпринесу@Mock/@InjectMocksПараметры аннотации найдены и инициализированы.

Наконец вWhitebox#newInstanceбудет генерироватьMockобъект.

Как правило, «нагромождение» выполняется при инициализации модульного теста, напримерwhen...thenReturn....генерироватьStubbing

Когда он вернется, ход укладки будет обновлен, и будет сгенерирован соответствующий прокси.

performStubbingпозвонюcreateMockСоздание прокси-объектов.

Он будет вызываться при работе в качестве проксиMockMakerпоколение классаMockобъект.

PowerMockMaker/CglibMockMakerвсе сбудетсяMockMakerИнтерфейс, в котором PowerMockito поддерживает различные методы перезаписи улучшения байт-кода. В настоящее время используется метод CGLIB.

       ClassImposterizer#createProxyClassиспользоватьCGLIBинструмент байт-кодаEnhanceКласс проксирует целевой класс модульного теста и вставляет методы для перехвата объекта класса.

Определен класс перехвата метода.

Таким образом, в будущем будет создан новый объект прокси-класса.При сопоставлении метода будет возвращен ожидаемый результат, потому что существует еще много реализаций всего фреймворка PowerMockito.Вот относительно простой пример для легкого понимания :

public class PowerMockito {

    private static Map<Invocation, Object> results = 
        new HashMap<Invocation, Object>();
    private static Invocation lastInvocation;

    public static <T> T mock(Class<T> clazz) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(new MockInterceptor());
        return (T)enhancer.create();
    }

    private static class MockInterceptor implements MethodInterceptor {
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            Invocation invocation = new Invocation(proxy, method, args, proxy);
            lastInvocation = invocation;
            if (results.containsKey(invocation)) {
                return results.get(invocation);
            }
            return null;
        }
    }

    public static <T> When<T> when(T o) {
        return new When<T>();
    }

    public static class When<T> {
        public void thenReturn(T retObj) {
            results.put(lastInvocation, retObj);
        }
    }
}

5. Советы по написанию модульных тестов

Вот несколько советов по написанию модульных тестов:

  • Менталитет надо исправлять.Написание одного теста не пустая трата времени, а для лучшего улучшения качества дизайна и реализации;
  • Фоновая разработка представляет собой иерархическую структуру, которая требует субподрядного управления тестовыми примерами и написания отдельных тестов по уровням;
  • Чтобы максимизировать охват кода, подумайте о наборах тестовых данных, таких как границы/исключения/сбои и т. д.;
  • Не злоупотребляйте Mock, вызывайте то, что должно быть вызвано, и вы не можете игнорировать первоначальную цель написания модульных тестов для достижения покрытия методов;
  • Более широкое использование инструментов генерации одиночных тестов для повышения эффективности написания, по этой причине автор также инкапсулирует компонент генерации.

6. Резюме

Эта статья основана на знаниях об одиночном тесте, недавно использовавшемся при разработке проекта.Она в основном описывает определение и функцию одиночного теста.Теперь используются основные принципы использования и реализации фреймворков Junit и Mock, разработанных на фоне Java. Наконец, автор описывает автора.Некоторый опыт и предложения по самостоятельному написанию юнит-тестов.

использованная литература