Должен ли я изменить соглашение об именах для своих модульных тестов?

В настоящее время я использую простое соглашение для своих модульных тестов. Если у меня есть класс с именем "EmployeeReader", я создаю тестовый класс с именем "EmployeeReader.Tests". Затем я создаю все тесты для класса в тестовом классе с такими именами, как:

  • Reading_Valid_Employee_Data_Correctly_Generates_Employee_Object
  • Reading_Missing_Employee_Data_Throws_Invalid_Employee_ID_Exception

и т.д.

Недавно я читал о разных типах именования, используемых в BDD. Мне нравится читаемость этого наименования, чтобы в итоге получить список тестов:

  • When_Reading_Valid_Employee (fixture)
    • Employee_Object_Is_Generated (метод)
    • Employee_Has_Correct_ID (метод)
  • When_Reading_Missing_Employee (fixture)
    • An_Invalid_Employee_ID_Exception_Is_Thrown (метод)

и т.д.

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

Ответ 1

Второй пример (имеющий привязку для каждой логической "задачи", а не один для каждого класса) имеет то преимущество, что для каждой задачи вы можете иметь разные логики SetUp и TearDown, что упрощает ваши индивидуальные методы тестирования и делает их более читаемым.

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

Ответ 2

Соглашение об именах, которое я использовал:

functionName_shouldDoThis_whenThisIsTheSituation

Например, это будут некоторые тестовые имена для функции "pop" стека

pop_shouldThrowEmptyStackException_whenTheStackIsEmpty

pop_shouldReturnTheObjectOnTheTopOfTheStack_whenThereIsAnObjectOnTheStack

Ответ 3

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

Ответ 4

Часть рассуждений по поводу второго соглашения об именах, на которое вы ссылаетесь, заключается в том, что вы одновременно создаете тесты и поведенческие спецификации. Вы устанавливаете контекст, в котором происходят события, и что на самом деле происходит тогда в этом контексте. (По моему опыту, наблюдения/тестовые методы часто начинаются с "should_", поэтому вы получаете стандартный формат "When_the_invoicing_system_is_told_to_email_the_client", "should_initiate_connection_to_mail_server".)

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

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

Ответ 5

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

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

public class MyClassSpecification
{
    protected MyClass instance = new MyClass();

    public class When_foobar_is_42 : MyClassSpecification 
    {
        public When_foobar_is_42() {
            this.instance.SetFoobar( 42 ); 
        }

        public class GetAnswer : When_foobar_is_42
        {
            private Int32 result;

            public GetAnswer() {
                this.result = this.GetAnswer();
            }

            public void should_return_42() {
                Assert.AreEqual( 42, result );
            }
        }
    }
}

который даст мне следующий результат в моем тестовом бегуне:

MyClassSpecification+When_foobar_is_42+GetAnswer
    should_return_42

Ответ 6

Я был на двух дорогах, которые вы описываете в своем вопросе, а также несколько других... Ваша первая альтернатива довольно проста и понятна для большинства людей. Мне лично нравится стиль BDD (ваш второй пример) больше, потому что он изолирует различные контексты и группы наблюдений в этих контекстах. Единственный реальный недостаток заключается в том, что он генерирует больше кода, поэтому начинать делать это кажется немного более громоздким, пока вы не увидите аккуратные тесты. Также, если вы используете наследование для повторного использования настройки прибора, вы хотите, чтобы testrunner выводил цепочку наследования. Рассмотрим класс "An_empty_stack", и вы хотите его повторно использовать, чтобы затем выполнить другой класс: "When_five_is_pushed_on: An_empty_stack" вы хотите, чтобы это как результат, а не только "When_five_is_pushed_on". Если ваш testrunner не поддерживает это, ваши тесты будут содержать избыточную информацию, например: "When_five_is_pushed_on_empt__stack: An_empty_stack", чтобы сделать вывод приятным.