Внедрение зависимостей, чтобы сделать ваш код тестируемым [A How-To Guide]

фото CDC

на Unsplash

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

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

Что такое инъекция зависимостей?

Инъекция зависимости — довольно простая, но невероятно мощная техника. Короче говоря, вместо функции, в которой жестко запрограммированы зависимости, функция позволяет разработчику, использующему функцию, передавать ей любые необходимые зависимости через аргументы.

Чтобы помочь закрепить концепцию, давайте вместе рассмотрим пример.

Разбор строки cookie

Допустим, вы хотите написать функцию JavaScript, которая может анализировать отдельные пары ключ-значение cookie из

document.cookie

строка.

Например, скажем, вы хотите проверить, есть ли cookie с именем

enable_cool_feature

и если его значение

true

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

К сожалению,

document.cookie

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

document.cookie.enable_cool_feature

Но, увы, мы не можем.

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

(Для справки, есть несколько библиотек JavaScript и пакетов, которые сделали именно это, поэтому не думайте, что вам нужно переписывать эту функцию самостоятельно в своем приложении, если вы этого не хотите.)

В качестве первого прохода нам может потребоваться определить простую функцию, подобную этой:

function getCookie(cookieName) { /* body here */ }

Эта функция позволит нам найти конкретное значение cookie, вызвав его следующим образом:

getCookie('enable_cool_feature')

Пример решения

Поиск Google «как разобрать строку cookie в JavaScript» показывает множество различных решений от разных разработчиков. В этой статье мы рассмотрим решение, предоставленное W3Schools, Это выглядит так:
export function getCookie(cookieName) {
  var name = cookieName + '='
  var decodedCookie = decodeURIComponent(document.cookie)
  var ca = decodedCookie.split(';')

  for (var i = 0; i < ca.length; i++) {
    var c = ca(i)
    while (c.charAt(0) == ' ') {
      c = c.substring(1)
    }

    if (c.indexOf(name) == 0) {
      return c.substring(name.length, c.length)
    }
  }

  return ''
}

Критика образца раствора

Теперь, что с этим не так? Мы не будем критиковать основную часть самого кода, а скорее рассмотрим следующую строку кода:

var decodedCookie = decodeURIComponent(document.cookie)
Функция

getCookie

имеет зависимость от

document

объект и на

cookie

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

Во-первых, что если по какой-то причине наш код не имеет доступа к

document

объект? Например, в среде Node,

document

является

undefined

, Давайте рассмотрим пример кода теста, чтобы проиллюстрировать это.

Давайте используем Jest в качестве нашей среды тестирования, а затем напишем два теста:

import { getCookie } from './get-cookie-bad'

describe('getCookie - Bad', () => {
  it('can correctly parse a cookie value for an existing cookie', () => {
    document.cookie = 'key2=value2'
    expect(getCookie('key2')).toEqual('value2')
  })

  it('can correctly parse a cookie value for an nonexistent cookie', () => {
    expect(getCookie('bad_key')).toEqual('')
  })
})

Теперь давайте запустим наши тесты, чтобы увидеть результат.

ReferenceError: document is not defined
о нет! В среде Node

document

не определен. К счастью, мы можем изменить наш конфиг Jest в нашем

jest.config.js

файл, чтобы указать, что наша среда должна быть

jsdom

, и это создаст DOM для нас, чтобы использовать в наших тестах.

module.exports = {
  testEnvironment: 'jsdom'
}
Теперь, если мы снова запустим наши тесты, они пройдут. Но у нас все еще есть небольшая проблема. Мы модифицируем

document.cookie

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

Например, если мы должны были написать

console.log(document.cookie)

в нашем втором тесте он все равно будет выводить

key2=value2

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

Чтобы решить эту проблему, мы могли бы сделать небольшую очистку после нашего первого теста

expect

заявление:

it('can correctly parse a cookie value for an existing cookie', () => {
  document.cookie = 'key2=value2'
  expect(getCookie('key2')).toEqual('value2')
  document.cookie = 'key2=; expires = Thu, 01 Jan 1970 00:00:00 GMT'
})
(Как правило, я бы посоветовал вам сделать уборку в

afterEach

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

document.cookie = ''

К сожалению.)

Вторая проблема с решением W3Schools возникает, если вы хотите проанализировать строку cookie, не установленную в данный момент в

document.cookie

свойство. Как бы ты это сделал? В этом случае вы не можете!

Существует лучший способ

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

Наша сигнатура функции будет немного отличаться от нашего исходного решения. На этот раз он примет два аргумента:

function getCookie(cookieString, cookieName) { /* body here */ }

Таким образом, мы можем назвать это так:

getCookie(<someCookieStringHere> 'enable_cool_feature')

Пример реализации может выглядеть так:

export function getCookie(cookieString, cookieName) {
  var name = cookieName + '='
  var decodedCookie = decodeURIComponent(cookieString)
  var ca = decodedCookie.split(';')

  for (var i = 0; i < ca.length; i++) {
    var c = ca(i)
    while (c.charAt(0) == ' ') {
      c = c.substring(1)
    }

    if (c.indexOf(name) == 0) {
      return c.substring(name.length, c.length)
    }
  }

  return ''
}
Обратите внимание, что единственная разница между этой функцией и исходной функцией заключается в том, что теперь функция принимает два аргумента и использует аргумент для

cookieString

при декодировании куки в строке 3.

Теперь давайте напишем два теста для этой функции. Эти два теста будут проверять те же вещи, что и наши первые два теста:

import { getCookie } from './get-cookie-good'

describe('getCookie - Good', () => {
  it('can correctly parse a cookie value for an existing cookie', () => {
    const cookieString = 'key1=value1;key2=value2;key3=value3'
    const cookieName = 'key2'
    expect(getCookie(cookieString, cookieName)).toEqual('value2')
  })

  it('can correctly parse a cookie value for an nonexistent cookie', () => {
    const cookieString = 'key1=value1;key2=value2;key3=value3'
    const cookieName = 'bad_key'
    expect(getCookie(cookieString, cookieName)).toEqual('')
  })
})

Обратите внимание, как мы можем полностью контролировать строку cookie, которую использует наш метод.

Нам не нужно полагаться на окружающую среду, мы не сталкиваемся с какими-либо трудностями при тестировании, и мы не должны предполагать, что мы всегда анализируем cookie непосредственно из

document.cookie

,

Намного лучше!

Вывод

Это оно! Внедрение зависимостей невероятно просто внедрить, и оно значительно улучшит ваш опыт тестирования, сделав ваши тесты легкими для написания и ваши зависимости легко поддельными. (Не говоря уже о том, что это помогает отделить ваш код, но это тема для другого дня.)

Спасибо за прочтение!

(Первоначально опубликовано Вот)



Источник: Внедрение зависимостей, чтобы сделать ваш код тестируемым [A How-To Guide]


Похожие материалы по теме: Внедрение зависимостей, чтобы сделать ваш код тестируемым [A How-To Guide]

Leave a comment