Программирование для автоматизации Flashcards
Martin Fowler about PageObject
Page objects are a classic example of encapsulation - they hide the details of the UI structure and widgetry from other components (the tests).
A page object wraps an HTML page, or fragment, with an application-specific API, allowing you to manipulate page elements without digging around in the HTML.
The basic rule of thumb for a page object is that it should allow a software client to do anything and see anything that a human can.
It should also provide an interface that’s easy to program to and hides the underlying widgetry in the window.
- So to access a text field you should have accessor methods that take and return a string,
- check boxes should use booleans,
- and buttons should be represented by action oriented method names.
The rule of thumb is to model the structure in the page that makes sense to the user of the application.
If you navigate to another page, the initial page object should return another page object for the new page
In general page object operations should return fundamental types (strings, dates) or other page objects.
There are differences of opinion on whether page objects should include assertions themselves, or just provide data for test scripts to do the assertions.
- Advocates of including assertions in page objects say that this helps avoid duplication of assertions in test scripts, makes it easier to provide better error messages, and supports a more TellDontAsk style API.
- Advocates of assertion-free page objects say that including assertions mixes the responsibilities of providing access to page data with assertion logic, and leads to a bloated page object.
I favor having no assertions in page objects. I think you can avoid duplication by providing assertion libraries for common assertions - which can also make it easier to provide good diagnostics.
Page objects are commonly used for testing, but should not make assertions themselves. Their responsibility is to provide access to the state of the underlying page. It’s up to test clients to carry out the assertion logic.
It’s common to write tests using some form of DomainSpecificLanguage, such as Cucumber or an internal DSL. If you do this it’s best to layer the testing DSL over the page objects so that you have a parser that translates DSL statements into calls on the page object.
Patterns that aim to move logic out of UI elements (such as Presentation Model, Supervising Controller, and Passive View) make it less useful to test through the UI and thus reduce the need for page objects.
Page objects are a classic example of encapsulation - they hide the details of the UI structure and widgetry from other components (the tests). It’s a good design principle to look for situations like this as you develop - ask yourself “how can I hide some details from the rest of the software?” As with any encapsulation this yields two benefits. I’ve already stressed that by confining logic that manipulates the UI to a single place you can modify it there without affecting other components in the system. A consequential benefit is that it makes the client (test) code easier to understand because the logic there is about the intention of the test and not cluttered by UI details.
https://martinfowler.com/bliki/PageObject.html
Когда нужно использовать классы, а когда функции?
Если класс инициализируется один раз и затем используется всего 1 раз - это должна быть функция
Zen of python
- Beautiful is better than ugly.
- Explicit is better than implicit.
- Simple is better than complex.
- Complex is better than complicated.
- Flat is better than nested.
- Sparse is better than dense.
- Readability counts.
- Special cases aren’t special enough to break the rules.
- Although practicality beats purity.
- Errors should never pass silently.
- Unless explicitly silenced.
- In the face of ambiguity, refuse the temptation to guess.
- There should be one– and preferably only one –obvious way to do it.
- Although that way may not be obvious at first unless you’re Dutch.
- Now is better than never.
- Although never is often better than right now.
- If the implementation is hard to explain, it’s a bad idea.
- If the implementation is easy to explain, it may be a good idea.
- Namespaces are one honking great idea – let’s do more of those!
Языки программирования для автоматизации
- Python - мощный язык, умеренно простой, легко писать скрипты любой сложности. Много различных библиотек для работы со всем, чем угодно. По факту 1 тест ранер - pytest.
- JavaScript/Typescript - язык фронтенда и на данный момент номер 1 по популярности. Безмерное количество разных библиотек. Выбирая JS/TS, вы увеличиваете шансы приблизиться к Shift left и начать делать тестирование фронтеда правильно. За последние годы появилось много библиотек: Jest, Cypress, Puppeteer, WebdriverIO. Недостаток - асинхронность, которая в тестах не нужна. Вторым недостатком являются странности языка по части работы некоторых функций.
- Старуха Java - классика. Почему java так популярна в мире автоматизации? Потому что до JavaScript эры именно Java была самым популярным языком. Selenium был написан для джавы и активнее всего развивался имеенно под эту экосистему. Материалов, лекций и документации больше всего именно под Java + Selenium. На стороне Java надежность, стабильность и наличие кучи библиотек. Большинство вакансий для QA Automation пока еще требуют знаний java. Есть и недостаток - громоздкость и сложность. Тесты - это то, что нужно уметь быстро создавать, быстро чинить и быстро удалять. Быстро создать тесты у вас получится, только если вы годами на ней пишете. Поставить Java, поставить Maven/Gradle, написать build script, настроить junit/testng, скомпилировать - это все занимает в разы больше времени, чем в питоне или Js. Kotlin? Он ничего принципиально не меняет.
- C# - есть достаточно большое количество проектов, написанных на C#/.Net. Этот язык не очень популярен в мире автоматизации, там есть 3-5 библиотек, которые покроют вам нужды автоматизации. Скажу честно, мне этот язык не нравится чисто по каким-то субьективным особенностям. Из недостаков - завязка на технологии и экосистему Майкрософт и Windows.
Playwright vs Selene
Плюсы playwright:
- Плейврайт использует протокол управления браузером на основе websocket а селениум - на основе http. Селениум что-то там питался тоже реализовать свой на основе websocket, но пока глухо…
- Соответственно плюсы те же что и у вебсокетов по сравнению с http - двунаправленная полнодуплексная передача сообщений.
- Селениум вынужден “опрашивать извне по http браузер в цикле при ожиданиях” а плейврайт опрашивает прямо на стороне браузера напрямую без затрат времени на передачу сообщений по http, и когда “ожидание закончилось” он просто маякует клиенту о конце.
- Это банально оптимальней по времени. По этому плейврайт в среднем на процентов 40 быстрее чем селен например… как раз изза того что ожидания в плейврайте работают эффективней…
- Кроме этого можно на плейврайте делать то что нельзя на селениуме, например подключится к живому уже открытому браузеру…
С другой стороны селениум можно для мобилок и даже десктоп использовать.
Но есть, есть очевидные плюсы у плейврайта в контексте одинаковых задач.
А есть просто отдельные нишевые задачи которые лучше делает либо прейврайт либо селениум.
Best automation practices
https://docs.cypress.io/guides/references/best-practices
Рекомендации и общепринятые договоренности в подборе имен (Python)
Исправить и дополнить. Прочитать на свежую голову
Именование любых сущностей в программировании – очень важно. От того как ты будешь называть свои модули и функции, переменные, классы, поля и методы – зависит понимание твоего кода другими программистами.
- «сложное для восприятия имя» будет замедлять работу с кодом,
- «имя приводящее к неправильному представлению о именованной сущности» рано или поздно приведет к появлению дефектов.
- Также, обычно важна «общепринятая в кругах программистов» стилистика, которой стоит следовать чтобы «поставлять как можно более привычный а следовательно и проще понимаемый большинству код»
- Рекомендуется нарушать любую такую общепринятую конвенцию только хорошо подумав, и поняв что плюсов от «нарушения» – больше чем минусов (например минусом может быть – неочевидность и дикость для других членов команды).
Начиная работать в новом для себя проекте, поинтересуйся о договоренностях в нейминге, принятых в конкретно этом проекте. Возможно, есть документ, описывающий это. Возможно, тебе придется сделать выводы самостоятельно, проанализировав уже существующий и использующийся код. В любом случае – стоит придерживаться «локальных» правил – так как для всей команды они уже очевидны.
Имя модуля?
- Отражает контекст использования функций, переменных, либо классов в этом модуле
- не содержит нумераций и прочей структурной информации
- нумерации и организационное структурирование лучше реализовывать «пакетами»
- начинается с маленькой буквы
- shortlowercase
-
longer_underscored_is_ok_if_improves_readability
### Имя класса?
-
- shortlowercase
- Отражает сущность которую представляют объекты этого класса
- Обычно характер сущности определяется – «поведениями обьекта» – представленными в виде его публичных методов
- не содержит нумераций и прочей структурной информации
- нумерации и организационное структурирование лучше реализовывать «пакетами» (python packages), а не уточнять в имени модуля либо класса
- начинается с большой буквы
Имя пакета python?
- отражает структурную информацию о коде внутри пакета, который по сути группирует python-модули «по контексту»
- shortlowercase
long_underscored_is_discouraged
- shortlowercase
Имя функции либо метода?
- отражает/описывает цель которую выполняет функция или метод
- начинается с маленькой буквы
Имя переменной?
- отражает/описывает то, «что сохраняет» переменная
- начинается с маленькой буквы
Имя константы?
-
SCREAMING_SNAKE_CASE
- большие буквы
- слова разделены подчеркиванием
### Имя тест-модуля или тест-класса?
- Имя тест-модуля либо тест-класса играет роль имени тест-суита (набора тест-кейсов) и должно отражать в общем – что тестируют методы данного тест-класса. При этом учитывая соглашения о именах для модулей и классов в Python.
- формат для имени тест-модуля
test_<[имя_приложения][_часть_приложения][_фича_приложения]>.py
- или
<[имя_приложения][_часть_приложения][_фича_приложения]>_test.py
- формат для имени тест-класса
-
Test<[ИмяПриложения][ЧастьПриложения][ФичаПриложения]>
- пример хорошего имени:
class TestSomeSocialNetworkPostManagement: ...
- пример хорошего имени:
-
- формат для имени тест-модуля
- При этом стоит учитывать контекст, и по возможности подбирать более лаконичные имена тест-классов.
- Обычно имя тест-класса начинают с Test, чтобы указывать, что это именно тест-класс а не обычный
- Часто, по умолчанию тест-раннеры настроены по умолчанию таким образом, что будут искать и запускать только те тесты, которые начинаются на Test (хотя это можно перенастроить).
- Иногда этой рекомендации не следуют, например когда симулируютBDD, где файлы со сценариями обычно называют «фичами» (features) или «спеками» (specs от specifications) – и тогда либо никакой приставки не пишут, либо используют
Spec
вместоTest
. - Рекомендуется нарушать любую общепринятую конвенцию только хорошо подумав, и поняв что плюсов от «нарушения» – больше чем минусов (например минусом может быть – неочевидность и дикость для других членов команды…)
- В имя тест-класса не нужно выносить структурную информацию (номер версии, вариант решения и т. п.) или свойства, которые не характеризуют тест-класс в целом с точки зрения тестового покрытия
- лучше для этого использовать отдельные пакеты
Пакеты в тестовом проекте?
- С помощью пакетов можно фиксировать любую структурную информацию, например
- если много тестов в разных стилях… Например, если часть тестов атомарные (с фокусом на одной фиче), а часть – стиля «end to end», покрывающие несколько фич в контексте определенного «пути пользователя» («user workflow», «user journey»), то можно сгрупировать тесты по следующим пакетам:
- пример 1:
- some-social-network-test/tests/e2e
- some-social-network-test/tests/atomic
- пример 2:
- some-social-network-test/tests/workflows
- some-social-network-test/tests/features
- пример 3:
- some-social-network-test/tests/journeys
- some-social-network-test/tests/features
- пример 1:
- если много тестов в разных стилях… Например, если часть тестов атомарные (с фокусом на одной фиче), а часть – стиля «end to end», покрывающие несколько фич в контексте определенного «пути пользователя» («user workflow», «user journey»), то можно сгрупировать тесты по следующим пакетам:
Тест-функции и тест-методы?
В первую очередь, имя тест-функции либо тест-метода играет роль имени тест-кейса и отображает то, что покрывают его шаги. Желательно, чтобы оно отвечалостандартным договоренностям Python по составлению имен функций или методов… Тогда его можно строить по одной из схем:
test_<фича | под_фича>_[_]<ожидаемый_ввод | тестовое_состояние>_[_]<ожидаемое_поведение>
Например:
test_register_new_user_existing_email_given_should_show_error_message()
либо чуть более выделяя структуру:
test_register_new_user\_\_existing_email_given\_\_should_show_error_message()
либо, хорошо подумав, все взвесив, и приняв решение пойти против стандартных соглашений в Python:
test_registerNewUser_ExistingEmailGiven_ShouldShowErrorMessage()
… аргументируя свое решение компактностью и тем, что зажимать шифт при слепом наборе проще чем тянуться правым мизинцем к подчеркиванию с зажатым левым мизинцем шифтом ;p
test_<фича | под_фича>_[_]<ожидаемое_поведение>_[_]<ожидаемый_ввод | тестовое_состояние>
Примеры:
test_register_new_user_shows_error_message_when_email_exists()
test_register_new_user\_\_shows_error_message\_\_when_email_exists()
test_registerNewUser_ShowsErrorMessage_WhenEmailExists()
given_<предусловия>_when_<ожидаемыеДействия>_then_<ожидаемое_поведение>
Для такого формата нужны будут дополнительные настройки тест-раннера, например для pytest:
content of pytest.ini in the root of your tests-project [pytest] python_functions = given_*
Примеры:
given_email_exists_when_register_new_user_then_error_message_is_shown()
given_email_exists\_\_when_register_new_user\_\_then_error_message_is_shown()
GIVEN_email_exists_WHEN_register_new_user_THEN_error_message_is_shown()
givenEmailExists_whenRegisterNewUser_thenErrorMessageIsShown()
Как-то длинновато получается? – Главное, чтобы было читабельно. Лаконичность это прекрасно, но только если не ухудшает читабельности;).
https://autotest.how/python/naming-guidelines-ru
Docstring
Elevator pitch - what it is you’re doing
init в классе
Init isn’t a constructor. It’s job is to initialize the instance variables
Self - is an instance, it’s already been made when init is called
Init - is initializer. It takes existing instance self
and populates it
Goal of instance variables - get the data you need into instance
Goal of init - populate that instance
Синтаксис классов в python
import math class Circle(object): version = '0.4' def \_\_init\_\_(self, radius): self.radius = radius def area(self): return math.py * self.radius ** 2.0 def perimeter(self): return 2.0 * math.pi * self.radius @classmethod def from bbd(cls, bbd): 'Construct a circle from a bounding box diagonal' radius = bbd / 2.0 / math.sqrt(2.0) return cls(radius) @staticmethod def angle_to_grade(self, angle): 'Convert angle in degreee to a percentage grade' return math.tan(math.radians(angle)) * 100.0 class Tire(Circle): def perimeter(self): return Circle.perimeter(self) * 1.25
- Cначала создаётся экземпляр класса (self)
- Затем вызывается init и он наполняет ими экземпляр класса self
- В методе - аргументом передаётся self
- 3.14 - заменяется переменной
- Версия указывается как переменная класса. Версии пишутся через str, иначе будет сбоить
- Суть class variable - to share data. Shared data stored at class level
- В питоне атрибуты доступны для изменения пользователями - python way (в java и c++ это не допустимо)
- Подкласс не использует инит, т.к. наследует его от родителя
- Class methods create alternative constructors (solves constructor wars).
- В случае войны конструкторов - все должны выйграть, для каждого создаются альтернативные конструкторы через @classmethod. И у всех дочерних классов должен быть к ним доступ - для этого возвращается cls(radius), он поддерживает дочерние классы.
- angle_to_grade - функция которая относится к кругу и не должна использоваться сферами или другими классами. Поэтому кладётся внутрь класса. Но, чтобы не создавать объект, а все равно иметь возможность использовать её - она создается как @staticmethod. Ей не нужно ничего знать про инстанс круга.
- Цель staticmethod - attach functions to classes. Делается, чтобы проще было найти функцию и чтобы люди использовали функцию в подходящем контексте
\_\_perimeter
- @property -
Flyweight design pattern: Slots
* supresses the instance dictionary
* makes class lightweight
* Делается в самом конце при значительном масштабировании, т.к. иначе теряется возможность inspect the dictionary, добавлять новые переменные на ходу
* Делая в конце - достигается эффективность использования памяти
- Inherit from
object()
- Instance variables for information unique to an instance
- Class variables - for data shared among all instances
-
Regular methods - need
self
to operate on instance data -
Class methods - implement alternative constructors. They need
cls
so they can create subclass instances as well -
Static methods - attach functions to classes. They don’t need either
self
orcls
. Static methods improve discoverability and require context to be specified - A property lets getter and setter methods be invoked automatically by attribute access. This allows Python classes to freely expose their instance variables.
- The
\_\_slots\_\_
variable implements the Flyweight Design Pattern by supressing instance dictionaries
https://youtu.be/HTLu2DFOdTg
Пирамида тестирования - martinfowler.com
Still, due to its simplicity the essence of the test pyramid serves as a good rule of thumb when it comes to establishing your own test suite. Your best bet is to remember two things from Cohn’s original test pyramid:
- Write tests with different granularity
- The more high-level you get the fewer tests you should have
Stick to the pyramid shape to come up with a healthy, fast and maintainable test suite: Write lots of small and fast unit tests. Write some more coarse-grained tests and very few high-level tests that test your application from end to end. Watch out that you don’t end up with a test ice-cream cone that will be a nightmare to maintain and takes way too long to run.
In the days of single page application frameworks like react, angular, ember.js and others it becomes apparent that UI tests don’t have to be on the highest level of your pyramid - you’re perfectly able to unit test your UI in all of these frameworks.
https://martinfowler.com/articles/practical-test-pyramid.html
Комментарии - использовать или нет?
Комментарии это зло по опыту всех разработчиков мира. Слишком сложно поддерживать. Поэтому используется принцип self documenting code - который сообщает о том, что происходит.
Поэтому все действия, все названия переменных, названия селектором - должны нести в себе смысл.
Неясные селекторы заворачиваются в осмысленно названные переменные
Page Object с ассертами или без
- Advocates of including assertions in page objects say that this helps avoid duplication of assertions in test scripts, makes it easier to provide better error messages, and supports a more TellDontAsk style API.
- Advocates of assertion-free page objects say that including assertions mixes the responsibilities of providing access to page data with assertion logic, and leads to a bloated page object.
Мартин Фаулер: I favor having no assertions in page objects. I think you can avoid duplication by providing assertion libraries for common assertions - which can also make it easier to provide good diagnostics.
Яков Крамаренко:
**В большинстве задач стоящих перед автоматизаторами - тесты которые нужно писать, это не те тесты, где стоит оставлять ассерты снаружи. Это больше подходит для юнит-тестов в большинстве случаев.
На UI - не удобно, хоть и соответствует основным принципам.**
- Special cases aren’t special enough to break the rules.
- Although practicality beats purity.
Когда не нужна автоматизация?
- Цикл жизни проекта крайне мал (неделю на написать и отдать навсегда)
- Функциональность находится в активной фазе разработки и требования постоянно меняются
- Трудозатраты на разработку автотестов несоизмеримы по времени с ручными проверками (часто происходит с UI)
- Мы чётко не можем сформулировать, какая от нее будет польза (автотесты ради автотестов - не работает)
Когда нужна автоматизация?
- Большое количество тестов на разных окружениях и платформах
- Быстрое предоставление разработчикам информации о состоянии системы
- Тестовые сценарии требуется проверять часто, да еще и на нескольких окружениях (браузерах, ОС) или устройствах (100-150 кейсов на 10 мобильных устройствах, а новые версии выкладываются пару раз в неделю)
- По другому: полный прогон всего набора тестов руками занимает слишком много времени
- Система под тестированием не имеет визуальной части или она еще не была сделана
- Необходимо проверять совместимость системы при переходе от версии к версии
- Есть большие требования по нагрузке и требуется провести тестирование производительности
- Хочется регулярных проверок работоспособности системы
Всегда ли нужна автоматизация?
Не всегда
Цикл жизни автотеста
- А нужно ли автоматизировать данный кейс?
- Проработка архитектуры
- Написание кода
- Отладка и проверка теста
- Описание и выкладка в репозиторий
- Проверка в “бою”
- Поддержка
- Удаление, если более не актуален
Циклы по словарю
Python
ключи
Это цикл по ключам
d = { "first": 1, "second": 2, "third": 3 } for item in d: pprint(item) Output: 'first' 'second' 'third'
for item in d.keys(): pprint(item) значения for item in d.values(): pprint(item) пара ключ+значение for item in d.items(): pprint(item) парсить ключи и значения в отдельные переменные сразу for key, value in d.items(): print(f"Ключ: {key}, Значение: {value}")
Получение значения из словаря через функции
users = [ {"name": "Oleg", "age": 32}, {"name": "Sergey", "age": 24}, {"name": "Stanislav", "age": 15}, {"name": "Olga", "age": 45}, {"name": "Maria", "age": 18}, ] def get_age(user): # получает и возвращает возвраст return user["age"] users.sort(key=get_age) # сортировка при помощи функции возвращающей возраст users.sort(key=lambda user: user["age"]) # аналогично тому, что выше, но через лямбда функцию users.sort(key=itemgetter("age")) # аналогично, но через питоновскую функцию, которая возвращает функцию как get_age
Путь к файлу python из которого запущен скрипт
os.path.abspath(\_\_file\_\_)
Абсолютный путь к файлу в текущей папке
os.path.abspath("download_file.py")
Определение пути к папке в которой лежит файл
current_file = os.path.abspath(\_\_file\_\_) current_dir = os.path.dirname(current_file)
Склеивание путей независимо от ОС
tmp_dir = os.path.join(current_dir, "tmp")
Как должна называться папка с проектом автотестов?
Название должно отражать суть. Т.е. имя сайта + какие тесты
Например: demowebshop_ui_tests