Pull to refresh

TDD: как правильно писать спецификации (describes)

Reading time 3 min
Views 4.2K
Написание спецификаций — утверждения или тестовые гипотезы — обычно обходят стороной в курсах TDD, поскольку тут мало программирования.

Однако они очень важны.

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

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

Во-первых, спецификации должны соотноситься с юзер стори в терминах. Если юзер стори пользуется термином «залогиниться», то ассерты не могут менять этот термин на authenticate/authorize/validate. Если стори написана неудачно, поменяйте ее.

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

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

Так же — один сценарий, как правило, описывает либо ui-событие, либо какой-то паттерн. Хорошие кандидаты на название компонента могут содержать название паттерна: Validator, Strategy, Builder, Transformer, Controller и тп. Или название события: Submitter, Login, ReadonlyOrderView и тп. Т.е. в зависимости от названия компонента должны быть определены и входные и выходные значения главной функции класса. Плохие названия: слишком абстрактные (Service,Component,Helper,Utility) и двойные (ValidatorAndRenderer).

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

Плохо:

@Test public void testValidatePassword(){}


Лучше:

@Test public void loginController_whenValidUsername_andValidPassword_shouldLogUserIn(){}


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

Для javascript-тестов то же самое, но там можно вкладывать условия, получается удобнее.
describe('login UI component', () => {
    describe('when username provided', () => {
        describe('when valid password', () => {
            it('should log user in', () => {
                ...
            });
        });
    });
});


Явно выраженные условия легче читать и находить пропущенные.

Кроме того, если условие не совпадает с написанным тестовым кодом, то код легко поправить.

Хороший describe — это, в общем, замена документации и комментированию кода. Он абстрактен, он не зависит от фреймворка и компилятора, и к нему следует относиться с той же серьезностью, как и к двум последним.

Сами условия должны быть консистентны в коде для легкого чтения, например GIVEN-WHEN-THEN-SHOULD и тп.

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

Errors first, nulls first, happy path last. Тогда не возникает желания закончить тестирование после одного валидного юз-кейса. Для UI-компонентов: рендеринг, события. Для stateful компонентов — последовательно все состояния.

Таким образом, структура хорошей читаемой спецификации выглядит так: 5-10 ошибочных, один валидный сценарий (или несколько вариаций одного сценария)



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

В-пятых, имплементация теста должна соответствовать тому, что написано в describe.
Хорошей практикой является разделение на arrange-act-assert, чтобы легко находить соответствие той или иной части имплементации. Текст ассертов и исключений, если таковой присутствует, должен быть тоже соотнесен с описаниями тестов, как правило, мы можем просто дублировать их.

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

Итак, последовательность написания спецификации может быть следующей.

  • прочитать юзер стори
  • назвать компонент и валидный сценарий
  • сформулировать условия для валидного сценария
  • дополнить спецификацию ошибочными сценариями, поместив их сверху
  • дать прочитать соседу и поправить пропущенное
  • имплементировать тесты и компонент один за другим, используя red-green-refactor цикл.
Tags:
Hubs:
+5
Comments 25
Comments Comments 25

Articles