Как улучшить код автоматизации тестирования

Автоматизация тестирования до смешного трудна. Независимо от того, автоматизируете вы на уровне системы, интеграции или отдельных модулей, это сложно от начала и до конца. Вы не знаете, с чего начать. А когда начинаете – не знаете, как действовать дальше.

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

Подпишитесь на наш ТЕЛЕГРАМ КАНАЛ ПО АВТОМАТИЗАЦИИ ТЕСТИРОВАНИЯ

1. Убедитесь, что ваш тест может провалиться

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

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

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

2. Не повторяйтесь

Как сказал соавтор Agile-манифеста Роберт С. Мартин, “Дублирование – главный враг хорошо спроектированной системы”. Плохих практик написания кода много, но редко какая-то другая плохая практика уничтожит ваш код быстрее, чем дублирование. Избежание дублирования должно быть одной из ваших основных задач при написании кода для автоматизации тестирования. Вы должны бороться с ним на всех уровнях.

Дублирование может быть просто несколькими строками кода, которые постоянно повторяются. Например, у вас может быть несколько тестов, которые выполняют примерно следующее:

[TestMethod]
[Description("Validate that user is able to fill out the form successfully using valid data.")]
public void Test1()
{
Driver = GetChromeDriver();
SampleAppPage = new SampleApplicationPage(Driver);
TheTestUser = new TestUser();
TheTestUser.FirstName = "Nikolay";
TheTestUser.LastName = "BLahzah";
EmergencyContactUser = new TestUser();
EmergencyContactUser.FirstName = "Emergency First Name";
EmergencyContactUser.LastName = "Emergency Last Name";
SetGenderTypes(Gender.Female, Gender.Female);
SampleAppPage.GoTo();
SampleAppPage.FillOutEmergencyContactForm(EmergencyContactUser);
var ultimateQAHomePage = SampleAppPage.FillOutPrimaryContactFormAndSubmit(TheTestUser);
AssertPageVisible(ultimateQAHomePage);
}
[TestMethod]
[Description("Fake 2nd test.")]
public void PretendTestNumber2()
{
Driver = GetChromeDriver();
SampleAppPage = new SampleApplicationPage(Driver);
TheTestUser = new TestUser();
TheTestUser.FirstName = "Nikolay";
TheTestUser.LastName = "BLahzah";
EmergencyContactUser = new TestUser();
EmergencyContactUser.FirstName = "Emergency First Name";
EmergencyContactUser.LastName = "Emergency Last Name";
SampleAppPage.GoTo();
SampleAppPage.FillOutEmergencyContactForm(EmergencyContactUser);
var ultimateQAHomePage = SampleAppPage.FillOutPrimaryContactFormAndSubmit(TheTestUser);
AssertPageVisibleVariation2(ultimateQAHomePage);
}

В этих автоматических тестах много дублирования. Если что-то изменится, например, тип драйвера или имя класса, представляющего SampleApplicationPage, или даже то, как вы инициализируете каждый из объектов TestUser, у вас возникнут проблемы.

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

Когда вы сталкиваетесь с такой проблемой, очевидное решение – просто обернуть общие строки кода в метод. В данном случае, поскольку это шаги настройки, мы также можем пометить этот метод атрибутом [TestInitialize] и позволить нашей системе тестирования вызывать этот метод перед каждым [TestMethod].

Например:

[TestInitialize]
public void SetupForEverySingleTestMethod()
{
    Driver = GetChromeDriver();
    SampleAppPage = new SampleApplicationPage(Driver);
    TheTestUser = new TestUser();
    TheTestUser.FirstName = "Nikolay";
    TheTestUser.LastName = "BLahzah";
    EmergencyContactUser = new TestUser();
    EmergencyContactUser.FirstName = "Emergency First Name";
    EmergencyContactUser.LastName = "Emergency Last Name";
}

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

[TestMethod]
[Description("Validate that user is able to fill out the form successfully using valid data.")]
public void Test1()
{
    SetGenderTypes(Gender.Female, Gender.Female);
    SampleAppPage.GoTo();
    SampleAppPage.FillOutEmergencyContactForm(EmergencyContactUser);
    var ultimateQAHomePage = SampleAppPage.FillOutPrimaryContactFormAndSubmit(TheTestUser);
    AssertPageVisible(ultimateQAHomePage);
}
[TestMethod]
[Description("Fake 2nd test.")]
public void PretendTestNumber2()
{
     SampleAppPage.GoTo();
     SampleAppPage.FillOutEmergencyContactForm(EmergencyContactUser);
     var ultimateQAHomePage = SampleAppPage.FillOutPrimaryContactFormAndSubmit(TheTestUser);
     AssertPageVisibleVariation2(ultimateQAHomePage);
}

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

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

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

Устранение дублирования имеет первостепенное значение для успеха вашего кода автоматизации тестирования. Всегда уделяйте время анализу своих тестов на наличие признаков дублирования и применяйте описанные выше стратегии для его устранения из кода.

3. Сохраняйте функции небольшими

“Первое правило функций – они должны быть маленькими. Второе правило функций заключается в том, что они должны быть еще меньше”, – Роберт С. Мартин.

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

Давайте рассмотрим пример имени метода:

public static string GetLanguageInUse(string content, string expectedLanguage = "")
{
    string language = string.Empty;
    content = content.Trim();
    var factory = new RankedLanguageIdentifierFactory();
    string directory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + @"\TestData\Core14.profile.xml";
    var identifier = factory.Load(directory);
    var languages = identifier.Identify(content);
    var mostCertainLanguage = languages.FirstOrDefault();
    if (mostCertainLanguage.Item1.Iso639_3 == "eng")
        language = "English";
    else if (mostCertainLanguage.Item1.Iso639_3 == "spa")
        language = "Spanish";
    if (language != expectedLanguage && expectedLanguage != "")
    {
        var letterOverrideLanguage = "";
        if (content.Length == 1 && content.All(char.IsLetter))
        {
            letterOverrideLanguage = expectedLanguage;
        }
        var mathOverrideLanguage = OverrideMathLanguage(content, expectedLanguage);
        var overrideLanguage = OverrideDetectedLanguage(content, expectedLanguage);
        if (letterOverrideLanguage != "")
            language = letterOverrideLanguage;
        else if (mathOverrideLanguage != "")
            language = mathOverrideLanguage;
        else if (overrideLanguage != "")
            language = overrideLanguage;
    }
    return language;
}

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

Этот метод создает фабрику, конкатенирует каталог XML, определяет язык, определяет mostCertainLanguage и то, на каком языке он находится – английском или испанском, а затем завершается сложным переопределением. Действительно ли этот метод получает используемый язык, как рекламируется? Совершенно неясно.

Теперь посмотрите на тот же метод, но отрефакторенный и сокращенный:

private static LanguageType GetLanguageInUse(string content, LanguageType expectedLanguage = LanguageType.NoLanguageDetermined)
{
    content = content.Trim();
    var language = DetermineLanguage(content);
    if (language != expectedLanguage && expectedLanguage != LanguageType.NoLanguageDetermined)
        language = OverrideUnexpectedLanguage(content, expectedLanguage);
    return language;
}

Здесь используется то же имя метода, принимающего содержимое и ожидаемый язык со значением по умолчанию “язык не определен”. Внутри мы видим, как строка содержимого обрезается (Trim()). Затем, используя это содержимое, определяется язык. И наконец, если определенный язык не удовлетворяет условиям в операторе if, этот язык заменяется ожидаемым языком.

Разительное отличие, не правда ли? Читаешь такой лаконичный метод – и душа радуется.

А вот еще один пример того, насколько мощным может быть небольшой метод:

public static bool IsContentInEnglish(string content)
{
    return GetLanguageInUse(content, LanguageType.English) == LanguageType.English;
}

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

Наличие небольших методов, состоящих менее чем из 10 строк, позволит вашему коду читаться, как рассказ. Именовать методы будет проще, потому что короткий метод, скорее всего, делает только одну вещь. Когда ваши коллеги заглянут внутрь вашего идеально названного метода, они будут приятно удивлены тем кодом, который там найдут. Вот это и есть признак настоящего мастера.

4. Пишите код только для текущих требований

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

“При чрезмерной инженерии большая проблема заключается в том, что людям становится трудно понять ваш код. В систему встроен какой-то элемент, который на самом деле не нужен, и человек, читающий код, не может понять, зачем он там, или даже как работает вся система (ведь она стала такой сложной)”, – пишет Max Kanat-Alexander в своей книге “Code Simplicity”.

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

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

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

Если вы просто будете следовать текущим требованиям, ваш тестовый код будет легче читать и понимать. Избавьте себя и своих коллег от лишней путаницы.

Большие улучшения

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

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

Перевод статьи Nikolay Advolodkin «4 rules to improve your test automation code».

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *