Рубрики
Без рубрики

Лучшее тестирование с инъекцией зависимостей

Я предпочитаю TDD для развития. Но во многих случаях слишком сложно писать тесты, когда мы используем внешние … Помечено тестированием, Python, Ruby, Teadercript.

Я предпочитаю TDD для развития. Но во многих случаях слишком сложно писать тесты, когда мы используем внешние услуги.

Проблема

Давайте представим, что у нас есть модель, которую мы используем для кэширования каких-либо хэшей в магазин, e. г. Redis. Мы будем использовать питон и pytest.

class Cache(Model):
    key: str
    data: dict

Нам необходимо реализовать простой репозиторий для управления кэшированием на хранение. Это может выглядеть так.

class CacheRepository:
    def save(self, cache):
        return storage.set(cache.key, cache.json())


def test_saves_cache_to_storage():
    cache = CacheFactory()
    repo = CacheRepository()

    repo.save(cache)

    assert storage.get(cache.key) == cache.json()

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

У нас много пакетов, которые могут помочь нам, чтобы насмешливую Редус, используя Pтойцы. Но самый простой способ – это рамки агностики. Это использование инъекций зависимостей.

Решение с ди

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

Посмотрите, как мы можем реализовать тот же код, использующий di. Похоже, предыдущая версия. Но теперь у нас есть больше места для борьбы с хранением.

class CacheRepository:
    def __init__(self, storage=storage):
        self.storage = storage

    def save(self, cache):
        return self.storage.set(cache.key, cache.json())


class MockStorage:
    _storage = {}

    def set(self, key, value):
        self._storage[key] = value

    def get(self, key):
        return self._storage.get(key)


def test_saves_cache_to_storage():
    storage = MockStorage()
    cache = CacheFactory()
    repo = CacheRepository(storage=storage)

    repo.save(cache)

    assert storage.get(cache.key) == cache.json()

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

Когда мы пишем интеграционные тесты, мы должны использовать реальное хранение. Но это еще одна история. Использование DI Мы можем проверить бизнес-логику с большим количеством быстрых модульных тестов и запустить только несколько медленных тестов, чтобы увидеть, как работает система вместе.

Примеры с других языков

С ruby и rspec. Чтобы получить ссылку для файла телеграммы, используя бот, мы должны предоставить токен бота в ссылке. Вместо того, чтобы раскрыть наши полномочия в тестировании, мы можем просто дать ему бросить ди.

class TelegramFile
  def initialize(bot:, file_id:)
    @bot = bot
    @file_id = file_id
  end

  def link
    "https://api.telegram.org/file/bot#{bot_token}/#{file_path}"
  end

  private

  attr_reader :bot, :file_id

  def file_path
    file.dig("result", "file_path")
  end

  def file
    bot.get_file(file_id: file_id)
  end

  def bot_token
    bot.token
  end
end

describe TelegramFile do
  describe "#link" do
    it "returns file link" do
      bot = instance_double("Telegram::Bot::Client", token: "_123", get_file: file_response)
      file = described_class.new(bot: bot, file_id: "file_id")

      expect(file.link).to eq "https://api.telegram.org/file/bot_123/photos/file_5.jpg"
    end
  end
end

С помощью tearpcript и шума. Мы реализуем простой помощник, который говорит, что включает в себя ссылку на сайт, необходимые метки UTM. Как у нас нет Document.location.Search В нашей тестовой среде мы можем просто передать его с помощью ди..

export class UtmMatcherService {
  constructor(
    private combinations: Record[],
    private search: string = document.location.search,
  ) {}

  hasMatches = (): boolean => {
    return this.includesAtLeastOneOfCombination()
  }
}

describe('UtmMatcherService', () => {
  describe('#hasMatches', () => {
    const combinations: Record[] = [
      {utm_source: 'stories', utm_medium: 'mobile'},
      {utm_source: 'facebook'},
    ]

    context('when we have one suitable utm', () => {
      const search = '?utm_source=facebook'

      it('returns true for one utm', () => {
        const matcher = new UtmMatcherService(combinations, search)

        expect(matcher.hasMatches).toBe(true)
      })
    })
  })
})

TLDR

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

Оригинал: “https://dev.to/andreieres/better-testing-with-dependency-injection-b4d”