Я предпочитаю 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”