Что нового
  • Что бы вступить в ряды "Принятый кодер" Вам нужно:
    Написать 10 полезных сообщений или тем и Получить 10 симпатий.
    Для того кто не хочет терять время,может пожертвовать средства для поддержки сервеса, и вступить в ряды VIP на месяц, дополнительная информация в лс.

  • Пользаватели которые будут спамить, уходят в бан без предупреждения. Спам сообщения определяется администрацией и модератором.

  • Гость, Что бы Вы хотели увидеть на нашем Форуме? Изложить свои идеи и пожелания по улучшению форума Вы можете поделиться с нами здесь. ----> Перейдите сюда
  • Все пользователи не прошедшие проверку электронной почты будут заблокированы. Все вопросы с разблокировкой обращайтесь по адресу электронной почте : info@guardianelinks.com . Не пришло сообщение о проверке или о сбросе также сообщите нам.

Знакомство С Фронтенд-тестированием. Часть Третья. E2e-тестирование

Sascha

Заместитель Администратора
Команда форума
Администратор
Регистрация
9 Май 2015
Сообщения
1,071
Баллы
155
Возраст
52
Рассказывает

Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.

, автор блога на Hackernoon


В

Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.

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

Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.

calculator, используя Mocha и

Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.

.

В этой части мы рассмотрим сквозное (E2E) тестирование: протестируем всё приложение целиком, причём сделаем это с точки зрения пользователя, по сути, автоматизируя все его действия.

В нашем случае приложение состоит только из фронтенда — бэкенда попросту нет, поэтому E2E-тестирование будет заключаться в открытии приложения в реальном браузере, выполнении набора вычислений и проверке валидности значения на экране.

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

Сколько нужно E2E-тестов?


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

Вторая причина — они медленные. Если их будет сотня, как юнит-тестов и интеграционных, то тестирование будет проходить очень долго.

Третья причина — непредсказуемое поведение E2E-тестов. О таком явлении есть

Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.

в блоге Google, посвященном тестированию. В юнит-тестах не наблюдается такого нестабильного поведения. Они могут то проходить, то падать — причем без видимых изменений, исключительно из-за I/O. Можно ли убрать непредсказуемость? Нет, но можно свести её к минимуму.

Чтобы избавиться от непредсказуемости, делайте как можно меньше E2E-тестов. Пишите один E2E-тест на десять других, и лишь тогда, когда они действительно необходимы.

Пишем E2E-тесты


Перейдём к написанию E2E-тестов. Нам нужны две вещи: браузер и сервер для нашего фронтенд-кода.

Для E2E-тестирования, как и для юнит-тестирования, мы будем использовать Mocha. Мы настроим браузер и веб-сервер, используя функцию before, и обнулим настройки при помощи функции after. Эти функции запускаются до и после выполнения всех тестов и настраивают окружение, которое могут использовать тестовые функции. Узнать о том, как они работают, можно в

Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.

.

Сперва взглянем на настройку веб-сервера.

Настройка веб-сервера в Mocha


Веб-сервер на Node? На ум сразу же приходит

Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.

, давайте посмотрим код:

let server

before((done) => {
const app = express()
app.use('/', express.static(path.resolve(__dirname, '../../dist')))
server = app.listen(8080, done)
})
after(() => {
server.close()
})

В функции before мы создаем express-приложение, указываем ему папку dist и прописываем слушать порт 8080. В функции after мы «убиваем» сервер.

Папка dist — это то место, где мы храним наши JS-скрипты и куда копируем HTML- и CSS-файлы. Вы можете увидеть, что мы делаем это в сборочном скрипте npm в package.json:

{
"name": "frontend-testing",
"scripts": {
"build": "webpack && cp public/* dist",
"test": "mocha 'test/**/test-*.js' && eslint test lib",
...
},

Это значит, что для E2E-тестов нужно сначала выполнить npm run build, а потом npm test. Да, это неудобно. В случае юнит-тестов этого делать не нужно, так как они запускаются под Node и не требуют трансляции и сборки.

Для полноты картины давайте взглянем на webpack.config.js, где описано, как Webpack должен делать сборку файлов:

module.exports = {
entry: './lib/app.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
...
}

Webpack будет читать наш app.js и собирать все необходимые файлы в bundle.js в папке dist.

Папка dist используется как в пользовательском окружении, так и в E2E-тестах. Это важно — запускать E2E-тесты нужно в средах, максимально похожих на «боевые».

Настройка браузера в Mocha


Наше приложение установлено на сервер — осталось лишь запустить для него браузер. Какую библиотеку мы будем использовать для автоматизации? Я обычно использую популярную selenium-webdriver.

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

const {prepareDriver, cleanupDriver} = require('../utils/browser-automation')

//...
describe('calculator app', function () {
let driver
...
before(async () => {
driver = await prepareDriver()
})
after(() => cleanupDriver(driver))

it('should work', async function () {
await driver.get('http://localhost:8080')
//...
})
})

В функции before мы готовим драйвер, а в after — очищаем его. Подготовка драйвера будет запускать браузер, а очистка — закрывать его. Заметим, что настройка драйвера происходит асинхронно и мы можем использовать async/await, чтобы сделать код красивее.

В тестовой функции мы открываем адрес http://localhost:8080, снова используя await, учитывая, что driver.get — асинхронная функция.

Так как же выглядят prepareDriver и cleanupDriver?

const webdriver = require('selenium-webdriver')
const chromeDriver = require('chromedriver')
const path = require('path')

const chromeDriverPathAddition = `:${path.dirname(chromeDriver.path)}`

exports.prepareDriver = async () => {
process.on('beforeExit', () => this.browser && this.browser.quit())
process.env.PATH += chromeDriverPathAddition

return await new webdriver.Builder()
.disableEnvironmentOverrides()
.forBrowser('chrome')
.setLoggingPrefs({browser: 'ALL', driver: 'ALL'})
.build()
}

exports.cleanupDriver = async (driver) => {
if (driver) {
driver.quit()
}
process.env.PATH = process.env.PATH.replace(chromeDriverPathAddition, '')
}

Это сложная штука. И я должен кое-что признать: этот код был написан кровью (о, и он работает только в Unix-системах). Он был написан при помощи Google, Stack Overflow и документации webdriver и сильно модифицирован методом научного тыка. Но он работает!

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

Первые две строки подключают webdriver — драйвер для браузера. Принцип работы Selenium Webdriver заключается в наличии API (в модуле selenium-webdriver, который мы импортируем в строке 1), который работает с любым браузером, и он полагается на драйверы браузера, чтобы… управлять различными браузерами. Драйвер, который я использовал, — chromedriver, импортированный в строке 2.

Драйвер Chrome не нуждается в браузере на машине: он фактически устанавливает свой собственный исполняемый файл Chrome, когда вы выполняете npm install. К сожалению, по некоторым причинам, которые я не могу понять, он не может найти его, и каталог chromedriver должен быть добавлен в PATH (это именно то, что не работает в Windows). Это мы делаем в строке 9. Мы также удаляем его из PATH на этапе очистки, в строке 22.

Итак, мы настроили драйвер браузера. Теперь пришло время настроить (и вернуть) веб-драйвер, что мы и делаем в строках 11–15. А поскольку функция build асинхронна и возвращает

Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.

, мы ждём её при помощи await.

Почему мы делаем это в строках 11–15? Причины скрыты туманом опыта. Не стесняйтесь копипастить — никаких гарантий не прилагается, но я использовал этот код некоторое время, и проблем не возникало.

Приступим к тестам


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

Разберём

Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.

по частям:

// ...
const retry = require('promise-retry')
// ...

it('should work', async function () {
await driver.get('http://localhost:8080')

await retry(async () => {
const title = await driver.getTitle()

expect(title).to.equal('Calculator')
})
//...

Пропустим установку, которую мы видели раньше, и перейдем к самой тестовой функции.

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

Перейдём к строке 9. Здесь мы просим браузер вернуть нам заголовок (используем await для ответа, потому что это асинхронно), а в строке 10 мы проверяем, что заголовок title имеет корректное значение.

Так почему мы повторяем это, используя модуль promise-retry? Причина очень важна, мы увидим, что и в остальной части теста браузер, когда мы попросим его что-то сделать, например, перейти по URL-адресу, сделает это, но асинхронно. Не позволяйте await одурачить вас! Мы ждём, пока браузер скажет: «OK, я сделал это», — а не конца операции.

Поиск элементов


Дальше, к следующей части теста!

const {By} = require('selenium-webdriver')
it('should work', async function () {
await driver.get('http://localhost:8080')
//...

await retry(async () => {
const displayElement = await driver.findElement(By.css('.display'))
const displayText = await displayElement.getText()

expect(displayText).to.equal('0')
})

//...

Теперь мы проверим, что первоначально display равен 0. Для этого найдем элемент, который содержит display — в нашем случае это класс display. Это мы делаем в строке номер 7 с помощью функции findelement объекта класса webdriver. Мы можем искать элементы с помощью методов By.id, By.css или

Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.

. Я обычно использую By.css — он принимает селектор и очень гибок в использовании, хотя By.javascript, вероятно, самый гибкий из них.

Как вы могли заметить, By импортирован из selenium-webdriver.

В строке 10 с помощью метода getText() мы получаем содержимое элемента и проверяем его. Помните, что нужно дожидаться (await) выполнения всех методов!

Пользовательский интерфейс


Настало время тестировать наше приложение — нажимать на цифры и операторы и проверять результат операций:

const digit4Element = await driver.findElement(By.css('.digit-4'))
const digit2Element = await driver.findElement(By.css('.digit-2'))
const operatorMultiply = await driver.findElement(By.css('.operator-multiply'))
const operatorEquals = await driver.findElement(By.css('.operator-equals'))

await digit4Element.click()
await digit2Element.click()
await operatorMultiply.click()
await digit2Element.click()
await operatorEquals.click()

await retry(async () => {
const displayElement = await driver.findElement(By.css('.display'))
const displayText = await displayElement.getText()

expect(displayText).to.equal('84')
})

Сначала мы находим элементы, на которые хотим нажать, в строках 2–4. Затем нажимаем на них в строках 6–7. В нашем тесте получилось выражение "42*2=". Затем мы повторяем процесс, пока не получим правильный результат, "84".

Выполнение всех тестов


Итак, у нас есть E2E-тесты и юнит-тесты, запустим их с помощью npm test:


Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.



Всё отлично!


Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.

.
 
Вверх