По материалам открытых уроков и выступлений Светланы Лебедевой, старшего инженера по тестированию ПО и преподавателя OTUS

Датаклассы в Python

Словари и дата-классы
Перед тем как перейти к обзору библиотек, рассмотрим различия словарей и датаклассов.

Словарь (dictionary): структура данных, которая позволяет хранить пары «ключ-значение». Ключ – неизменяемый тип данных, обычно – строка, но ключом может быть и целое число, число с точкой или даже «кортеж». Данные из словаря мы извлекаем по ключу. Скажем сразу: в этой статье речь идёт о базовом словаре (dict), а не о более продвинутой структуре данных TypedDict. 

Обычно инженеры по автоматизации тестирования работают со структурой JSON, которая проще всего конвертируется в обычный dict. У dict нет чёткой структуры и валидации типов данных; значения могут быть самых разных типов: как изменяемых, так и неизменяемых, и при инициализации словаря мы не можем задать ограничения по добавляемым в него типам данным. 

Датаклассы: сравнительно новая фича в Python. Как видно из названия, эти классы представляют набор инструментов для хранения и обработки данных.

Вот основные отличия датаклассов от обычных классов:

  • При использовании датаклассов не нужно думать о методе __init__ (метод, который вызывается при создании объекта класса).
  • Не нужно думать о методах, которые позволяют сравнивать два объекта одного класса.
  • Код становится чище и красивее.
  • Чтобы создать датакласс, нужно просто использовать декоратор @dataclass.
  • Есть метод __post_init__, который позволяет добавить данные уже после инициализации объекта этого класса.

Модуль dataclasses также включает в себя функцию field, которая позволяет более гибко настраивать поля датаклассов. С помощью field можно задавать параметры по умолчанию, функции для создания значений по умолчанию (или «фабрики») и параметры, которые влияют на генерацию методов.

Пример использования датакласса и функции field: 

from dataclasses import dataclass, field

from typing import List

@dataclass

class Student:

   name: str

   age: int

   grades: List[int] = field(default_factory=list)

Здесь мы создаём класс для хранения данных ученика, где поле grades по умолчанию – пустой список. 

Для валидации данных в датаклассах можно использовать метод __post_init__, который вызывается сразу после генерации __init__. Это позволяет выполнять дополнительную логику. Например, проверку типов и значений:

from dataclasses import dataclass, field

@dataclass

class Product:

   name: str

   price: float

   quantity: int = field(default=0)

   def __post_init__(self):

       if self.price <= 0:

           raise ValueError("Цена должна быть больше 0")

       if self.quantity < 0:

           raise ValueError("Количество должно быть больше 0")

# Правильное использование

product = Product(name="Laptop", price=1000.0, quantity=5)

# Неправильное использование вызовет исключение

try:

   product_with_negative_price = Product(name="Laptop", price=-1000.0)

except ValueError as e:

   print(e)  # Цена должна быть больше 0

Библиотеки Faker и Pydantic упрощают процесс валидации и генерации данных, а потому – очень полезны в процессе автоматизации тестирования.

Функционал библиотеки Pydantic

Pydantic – библиотека, в основе которой лежит концепция датаклассов или «моделей». Библиотека позволяет удобно хранить и валидировать данные прямо в момент создания объекта класса. Кроме того, в Pydantic можно превращать данные, как из модели данных – в JSON, так и наоборот: из JSON – в модель данных. 

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

Возможности Pydantic

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

Скорость: логика валидации Pydantic написана на Rust. Поэтому Pydantic – одна из самых быстрых библиотек для валидации данных в Python.

JSON Schema: модели Pydantic могут генерировать JSON Schema, что облегчает интеграцию с другими инструментами.

Строгий и гибкий режимы: Pydantic может работать в строгом режиме (strict=True), где данные не преобразуются, или в гибком режиме (strict=False), где Pydentic при необходимости пытается привести данные к правильному типу.

Настройка: Pydantic позволяет применять пользовательские валидаторы и сериализаторы для изменения обработки данных разными способами.

Экосистема: около 8000 пакетов на PyPI используют Pydantic.

Распространение: Pydantic скачивается более 70 миллионов раз в месяц; используется всеми компаниями FAANG и 20 из 25 крупнейших компаний NASDAQ

Подробнее о возможностях библиотеки Pydantic: смотреть фрагмент конференции «OTUS CONF: Тестирование» 

Недостатки 

  • Не входит в стандартную библиотеку Python
  • Требует много времени на освоение из-за обширной документации

Чтобы использовать модель Pydantic, достаточно «унаследовать» новый класс от класса BaseModel, после чего обширный функционал библиотеки станет доступен. 

Пример:

from pydantic import BaseModel, Field, field_validator

from typing import List

class Product(BaseModel):

   name: str

   price: float = Field(..., gt=0, description="Price must be greater than zero")

   quantity: int = Field(default=0, ge=0, description="Quantity must be greater than zero")

   tags: List[str] = []

   @field_validator('name')

   @classmethod

   def name_must_not_be_empty(cls, v):

       if not v.strip():

           raise ValueError('Name must not be empty')

       return v

   @field_validator('tags')

   @classmethod

   def tags_must_be_non_empty(cls, v):

       if not v:

           raise ValueError('Tags must not be empty')

       return v

# Правильное использование

try:

   product = Product(name="Laptop", price=1000.0, quantity=5, tags=["electronics", "computers"])

   print(product)

except ValueError as e:

   print(e)

# Неправильное использование вызовет исключение

try:

   invalid_product = Product(name="  ", price=-1000.0, quantity=-1, tags=[])

except ValueError as e:

   print(e)

В этом примере:

  • Класс Product наследуется от BaseModel Pydantic.
  • Поля price и quantity используют встроенные валидаторы gt (greater than) и ge (greater or equal) для проверки значений.
  • Пользовательский валидатор для поля name проверяет, что имя не пустое.
  • Пользовательский валидатор для поля tags проверяет, что каждый тег не пустой.

Функционал библиотеки Faker

Faker – библиотека, которая генерирует реалистичные тестовые данные с разной локализацией. У этой библиотеки много провайдеров: провайдер адреса, провайдер номеров автомобилей, банковской информации, штрихкодов, эмоджи, текстовый провайдер, провайдер для генерации географических данных и проч.

Также у Faker много локализаций с данными, специфичными для конкретной страны.

Формат JSON

Часто в проекте нужно сгенерировать какой-то JSON, который должен содержать данные о пользователе. И вам приходится придумывать фамилию, имя, отчество, номер карты, адрес, email. Чтобы не тратить время, вы можете сделать всё это с помощью библиотеки Faker. Кроме того, с помощью Faker можно наполнять базы данных и делать тестовые файлы.

Подробнее про использование Faker: смотреть запись вебинара на Youtube

Faker легко установить и использовать

->стандартная команда

!pip install Faker 

Пишем код с использованием Faker:

from faker import Faker

faker = Faker()

print(f'name: {faker.name()}')  # name: Arthur Patton

print(f'address: {faker.address()}')  # address: 0638 Larsen Way, Tylermouth, CA 48344

print(f'text: {faker.sentence()}')  # text: While per budget up technology design.

Теперь объединим две библиотеки, чтобы сгенерировать простой JSON-объект:

from pydantic import BaseModel, Field

from faker import Faker

from typing import List

# Инициализация Faker

faker = Faker()

# Функции для генерации данных с помощью Faker

def fake_name():

   return faker.name()

def fake_age():

   return faker.random_int(min=18, max=25)

def fake_grades():

   return [faker.random_int(min=60, max=100) for _ in range(5)]

# Определение модели данных с использованием Pydantic

class Student(BaseModel):

   name: str = Field(default_factory=fake_name)

   age: int = Field(default_factory=fake_age)

   grades: List[int] = Field(default_factory=fake_grades)

# Создание экземпляра модели Student с автоматической генерацией данных и их валидация

try:

   student = Student()

   print(student)

except ValueError as e:

   print(e)

# Конвертация объекта Pydantic в JSON

student_json = student.model_dump()

print(student_json)

>>> {'name': 'Maria Riddle', 'age': 23, 'grades': [81, 87, 74, 92, 92]}

Дополнительно

  • В качестве функции-аргумента для default_factory нужно использовать функцию, не принимающую аргументы. Если аргументы всё-таки нужно передать, можно воспользоваться lambda-функцией. Например: Field(default_factory=lambda: faker.random_int(min=60, max=100))
  • Датакласс можно превратить в словарь с помощью функции asdict. В модели Pydantic для этого используется метод model_dump()
  • Faker и Pydantic не входят в стандартный пакет Python

Больше о полезных библиотеках для автоматизации тестирования – на курсе Python QA Engineer