Статьи

Модульное тестирование 101: создание гибкого тестового кода

Вступление

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

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

Академический против Реальной Жизни

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

Итак, вопрос в том, что нам делать, когда мы выходим за пределы примеров и начинаем тестировать реальные приложения?

Сценарий

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

Гибкая структура

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

Краткий ответ: Нет. Вы этого не делаете.

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

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

Теперь, как это выглядит в коде?

Код модульного теста 

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

[TestFixture]
partial class OnOrdersService 
{
    // More code here on a second...
}

Класс тестового комплекта компонентов использует следующее соглашение об именах:

public [partial] class On[ComponentName] { }

Таким образом, при чтении названия у нас есть четкое представление о том, что является целевым компонентом, который будет тестироваться комплектом. Сразу после объявления класса набора тестов компонентов мы начинаем определять класс тестового набора компонентов. Это будет класс, вложенный в класс набора тестов возможностей.

[TestFixture]
partial class OnOrdersService
{
    [TestFixture]
    public class WhenPlacingOrders
    {
        // Component feature test methods to be added here soon...
    }
}

Класс тестового комплекта компонентов компонента использует следующее соглашение об именах:

public class When[ActionPerformed][FeatureName] {}

Конечно, следующее, что нужно сделать, это добавить несколько методов тестирования. Давайте расширим наш пример: 

[TestFixture]
partial class OnOrdersService
{
    [TestFixture]
    public class WhenPlacingOrders
    {
        [Test]
        public void ShouldReturnOrderIdOnValidOrderPlaced()
        {
            // Test code for a success scenario...
        }
        
        [Test]
        public void ThrowErrorOnInvalidOrderPlaced()
        {
            // Test code for a failure scenario...
        }
    }
}

Методы тестирования сценария успеха используют следующее соглашение:

public void Should[ExpectedOutcome]On[ScenarioCondition]();

.. и сценарий сбоя использования:

public void Should[ExpectedFailure]On[FailureConditions]();

Прежде чем вы перестанете читать и гневно кричать на эти классные имена классов, давайте попробуем текстуально прочесть нашу иерархию:

При оформлении заказов … при оформлении заказов … должен возвращать идентификатор заказа на действующий заказ.

Итак, к настоящему времени вы должны получить более четкое представление о том, чего мы пытаемся достичь здесь. Управляя структурами классов (частичными и вложенными классами), мы можем организовать наши тесты по компонентам, а затем по компонентам, сохраняя интуитивно понятную и читаемую структуру. Это позволит нам сохранять тестовые классы небольшими и легко обслуживаемыми. Если вы не доверяете мне в предыдущем примере, вот как выглядит тестер Visual Studio при использовании нашей удобной структуры классов.

    Теперь допустим, что мы хотели бы добавить тесты, чтобы охватить метод обслуживания, который отменяет заказы. В этом случае мы создадим новый частичный класс, используя то же OnOrdersServiceимя класса. Затем мы создаем новый вложенный класс с именем WhenCancellingOrders.

Теперь у нас есть класс тестового набора компонентов, разделенный на два файла. Каждый файл будет содержать набор тестов компонентов одного компонента. 

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

On[ComponentName]When[ActionPerformed][FeatureName].cs

Например:

OnOrdersServiceWhenPlacingOrders.cs
OnOrdersServiceWhenCancellingOrders.cs

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

Вывод

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