Многие тестовые классы или один тестовый класс со многими методами?

У меня есть PersonDao, с которым я пишу модульные тесты.

В PersonDao существует около 18-20 методов формы -

    getAllPersons() 
    getAllPersonsByCategory()
    getAllPersonsUnder21() etc

Мой подход к тестированию заключался в создании PersonDaoTest с примерно 18 тестовыми методами, которые тестировали каждый из методов в PersonDao

Затем я создал PersonDaoPaginationTest, который тестировал эти 18 методов, применяя параметры разбивки на страницы.

Это в любом случае против лучших методов TDD? Мне сказали, что это создает путаницу и противоречит лучшим практикам, поскольку это нестандартно. Было предложено слияние двух классов в PersonDaoTest.

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

Ответ 1

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

Но в ответ на ваш вопрос, если вы обнаружите, что у вас есть класс, который вы хотите оставаться сложным, то отлично использовать несколько тестовых классов в качестве способа организации большого количества тестов. Ответ @Gishu от группировки тестов по их установке является хорошим подходом. @Ryan ответ группировки на "грани" или функции - еще один хороший подход.

Ответ 2

Невозможно дать вам исчерпывающий ответ, не глядя на код... кроме использования того, что кажется вам последовательным и вашей командой.

Я обнаружил, что групповые тесты , основанные на их настройке, хорошо работают в большинстве случаев. т.е. если 5 тестов требуют одинаковой настройки, они обычно хорошо вписываются в тест-арматуру. если для 6-го теста требуется другая настройка (более или менее), выведите ее в отдельное тестовое оборудование.

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

Ответ 3

Мои 2 цента: когда у вас есть большой класс, похожий на тот, который имеет разные "грани", например, разбиение на страницы, я считаю, что он часто может сделать более понятные тесты, чтобы не упаковать их все в один класс. Я не могу претендовать на то, чтобы быть гуру TDD, но я, естественно, практикую тестовое развитие. Я не делаю этого часто, но это также не так уж и редко, что я напишу больше, чем один тестовый класс для определенного класса. Однако многие люди забывают о хороших методах кодирования, таких как разделение проблем при написании тестов. Я не уверен, почему.

Ответ 4

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

Однако вы можете рассмотреть несколько вещей:

Ваши методы кажутся немного "чрезмерно конкретными" и могут использовать некоторую абстракцию или обобщение, например, вместо getAllPersonsUnder21() считать getAllPersonsUnder(int age)

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

@Test
public void testGetAllPersons() {
    assertMultipleHits(new Callable<List<?>> () {
        public List<?> call() throws Exception {
            return myClass.getAllPersons(); // Your call back is here
        }
    });    
}

public static void assertMultipleHits(Callable<List<?>> methodWrapper) throws Exception {
    assertTrue("failure to get multiple items", methodWrapper.call().size() > 0);
}

Этот статический метод может использоваться любым классом для проверки того, возвращает ли "какой-то метод" несколько обращений. Вы можете расширять это, чтобы выполнять множество тестов по одному и тому же обратному вызову, например, запускать его с подключением БД и без него, проверяя, что он ведет себя правильно в каждом случае.

Ответ 5

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

public SignUpTest()
{
   public SignUpTest(Map<String,Object> data){}
   public void step_openSignUpPage(){}
   public void step_fillForm(){}
   public void step_submitForm(){}
   public void step_verifySignUpWasSuccessfull(){}
}

Все шаги зависят, они следуют указанному порядку, и если кто-то выйдет из строя, другие не будут выполнены.

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

Требования были примерно такими:

  • Тесты должны управляться данными, то есть выполнять те же тесты параллельно с разными входами.
  • Тесты должны выполняться и в разных браузерах параллельно. Таким образом, каждый тест будет запускать "input_size x browsers_count" раз параллельно.
  • Тесты будут сосредоточены в веб-рабочем процессе, например, "зарегистрируйтесь с достоверными данными", и они будут разделены на меньшие единицы измерения для каждого шага рабочего процесса. Это облегчит поддерживать и отлаживать (когда у вас есть сбой, он скажет: SignUpTest.step_fillForm(), и вы сразу узнаете, что не так).
  • Шаги тестов используют один и тот же тестовый ввод и состояние (например, идентификатор созданного пользователя). Представьте, если вы поместите тот же класс шаги различных тестов, например:

    public SignUpTest()
    {       
       public void signUpTest_step_openSignUpPage(){}
       public void signUpTest_step_step_fillForm(){}
       public void signUpTest_step_step_submitForm(){}
       public void signUpTest_step_verifySignUpWasSuccessfull(){}
    
       public void signUpNegativeTest_step_openSignUpPage(){}
       public void signUpNegativeTest_step_step_fillFormWithInvalidData(){}
       public void signUpNegativeTest_step_step_submitForm(){}
       public void signUpNegativeTest_step_verifySignUpWasNotSuccessfull(){}
    } 
    

    Тогда, имея в том же классе состояний, принадлежащих к 2 тестам, будет беспорядок.

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