Русский
Русский
English
Статистика
Реклама

Mocha

Тестирование KotlinJS фреймворки, корутины и все-все-все

17.01.2021 00:16:40 | Автор: admin

Kotlin - блестящий проект. Изначально задуманный как просто JVM-язык, впоследствии он получил поддержку компиляции под все мейнстримные платформы, среди которых -- JavaScript.

Вводная. У меня есть пет-проект -- сайт и API-платформа для комьюнити по игре Elite: Dangerous. Бэкенд - на Kotlin/JVM (Ktor+Hibernate), фронтенд - на Kotlin/JS (KVision+Fomantic UI). О пет-проекте я расскажу как-нибудь потом, а о фронте поподобрнее.

  • KVision - фронтэнд-фреймворк для Kotlin, объединяющий в себе идеи из различных десктопных фреймворков (от Swing и JavaFX до WinForms и Flutter) и синтаксические возможности Kotlin, например, DSL-билдеры.

  • Fomantic-UI - форк Semantic-UI, компонентного веб-фреймворка для HTML/JS, который можно сравнить с Bootstrap, только Fomantic будет поинтереснее.

Не так давно я загорелся мыслью связать эти два мира и написать библиотеку для KVision, которая бы, как минимум, облегчила написание KVision-страниц с Fomantic-элементами. И, как подобается open source проекту, я планировал покрыть библиотеку тестами. Вот об этом приключении и будет эта статья.

Код

В первую очередь определимся с задачей. Есть у нас на руках следующий код простой Fomantic-кнопки:

package com.github.kam1sh.kvision.fomanticimport pl.treksoft.kvision.html.*import pl.treksoft.kvision.panel.SimplePanelopen class FoButton(text: String) : Button(text = text, classes = setOf("ui", "button")) {    var primary: Boolean = false        set(value) {            if (value) addCssClass("primary") else removeCssClass("primary")            field = value        }}fun SimplePanel.foButton(    text: String,    init: (FoButton.() -> Unit)? = null): FoButton {    val btn = FoButton(text)    init?.invoke(btn)    add(btn)    return btn}    

И есть парочка тестов:

package com.github.kam1sh.kvision.fomanticimport kotlin.test.BeforeTestimport kotlin.test.Testimport kotlin.test.assertEqualsimport kotlinx.browser.documentimport kotlinx.coroutines.*import pl.treksoft.kvision.panel.ContainerTypeimport pl.treksoft.kvision.panel.Rootclass FoButtonTest {    lateinit var kvapp: Root    @BeforeTest    fun before() {        kvapp = Root("kvapp", containerType = ContainerType.NONE, addRow = false)            }    @Test    fun genericButtonTest() {        kvapp.foButton("Button")        assertEqualsHtml("""...""")    }    @Test    fun buttonPrimaryTest() {        val btn = kvapp.foButton("Button") { primary = true }        assertEqualsHtml("""...""")        btn.primary = false        assertEqualsHtml("""...""")    }}fun assertEqualsHtml(expected: String, message: String? = null) {    val actual = document.getElementById("kvapp")?.innerHTML    assertEquals(expected, actual, message)}

Другими словами: "вшиваем" KVision в div с id=kvapp, создаём кнопку и потом ассертим её HTML-код.

Вопрос. Откуда должен взяться первоначальный div? Можно просто добавить HTML-код через какой-нибудь document.body?.insertAdjacentHTML(...), но что, если нам очень-очень хочется добавить прямо свою страничку вроде такой?

<source lang="html"><!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <script src="http://personeltest.ru/aways/cdn.jsdelivr.net/npm/jquery@3.3.1/dist/jquery.min.js"></script>    <link rel="stylesheet" type="text/css" href="http://personeltest.ru/aways/cdn.jsdelivr.net/npm/fomantic-ui@2.8.7/dist/semantic.min.css">    <script src="http://personeltest.ru/aways/cdn.jsdelivr.net/npm/fomantic-ui@2.8.7/dist/semantic.min.js"></script></head><body>    <main>        <div id="kvapp">        </div>    </main></body></html></source>

You've lost karma

Давайте сначала обратимся к разделу тестирования документации Kotlin/JS.
For browser projects, it downloads and installs the Karma test runner with other required dependencies; for Node.js projects, the Mocha test framework is used.
Ага. Karma и Mocha. Также говорится, что конфигурируется Karma через js-скрипты в папке karma.config.d.

После поиска по документации Karma по конфигам, приходим методом проб и ошибок к такому конфиг-скрипту:

// karma.config.d/page.jsconfig.set({  customContextFile: "../../../../../src/test/resources/test.html"})

Файл test.html, в моём случае, находится по пути src/test/resources/test.html. Из-за того, что Karma запускается в каталоге build/js/packages/kvision-fomantic-test/node_modules, нам для начала надо подняться на пять каталогов повыше.

Всё готово, верно? Запускаем ./gradlew browserTest, ииии... получаем Disconnected (0 times), because no message in 30000 ms.

Это всё потому, что наша HTML-страница несколько отличается от оригинальной, в которой выполняется особый JS-код. Оригинал можно посмотреть в build/js/node_modules/karma/static/context.html.

Копируем недостающий код в место сразу перед main-блоком:

<!-- The scripts need to be in the body DOM element, as some test running frameworks need the body     to have already been created so they can insert their magic into it. For example, if loaded     before body, Angular Scenario test framework fails to find the body and crashes and burns in     an epic manner. --><script src="context.js"></script><script type="text/javascript">    // Configure our Karma and set up bindings    %CLIENT_CONFIG%    window.__karma__.setupContext(window);    // All served files with the latest timestamps    %MAPPINGS%</script><!-- Dynamically replaced with <script> tags -->%SCRIPTS%<!-- Since %SCRIPTS% might include modules, the `loaded()` call needs to be in a module too.This ensures all the tests will have been declared before karma tries to run them. --><script type="module">    window.__karma__.loaded();</script><script nomodule>    window.__karma__.loaded();</script>

Запускаем, и... прикона, работает.

Корутины мои корутины

Но это всё более-менее просто. А если у нас есть корутины? Типа, HTTP-клиент Ktor или просто нам надо добавить задержку. Вот в Python мы бы просто повесили модификатор async, поставили к pytest плагин pytest-async, и всё.

Попробуем навесить suspend на функцию теста.

> Task :compileTestKotlinJs FAILED

e: ...src/test/kotlin/com/github/kam1sh/kvision/fomantic/FoButtonTest.kt: (44, 5): Unsupported [suspend test functions]

- Gradle

Хоба. Низзя. Тикет ещё не закрыт.

Ладно, тогда выполним весь код теста в runBlocking {}. Однако...

runBlocking эксклюзивен для Kotlin/JVM.

Это проблема, тоже есть тикет, правда, закрытый, мол, это by design. В качестве решения предлагается использовать GlobalScope.promise, и в принципе с ним можно написать runBlocking в одну строку:

fun runBlocking(block: suspend (scope: CoroutineScope) -> Unit) = GlobalScope.promise { block(this) }

Однако этого недостаточно. Если вы напишете такой код, у вас в определённый момент тесты начнут отваливаться по таймауту. Можно в Karma поднять таймаут следующим способом:

config.set({  client: {    mocha: {      timeout: 9000    }  }})

Но вечно поднимать таймауты не получится. Это просто workaround.

Mocha, из которой, на самом деле, и запускается код тестов, имеет два решения:

  • Делать функцию тестов, которые принимают done-колбэк, и дёргать колбэк по завершении теста.

  • Делать функцию теста такой, чтобы она возвращала Promise.

К сожалению, если вы попробуете добавить колбэк в объявление параметров метода, то ничего хорошего вы не получите. Точнее говоря, такой коллбэк в kotlin-test-js не поддерживается.
Второе, увы и ах, тоже не сделать. Ну, тест может возвращать Promise, но в Mocha он как бы не будет передан.

Что нам, собственно, остаётся? Как связать два мира -- мир корутин и мир тестирования?

Встречайте Волшебника из Канзаса - Kotest. Его разработчики всё за нас решили.

Давайте попробуем. Меняем только зависимости и исходники тестов, конфигурация будет прежней.

// build.gradle.ktstestImplementation("io.kotest:kotest-assertions-core-js:4.3.2")testImplementation("io.kotest:kotest-framework-api-js:4.3.2")testImplementation("io.kotest:kotest-framework-engine:4.3.2")
class FoButtonTest : FunSpec({    var kvapp: Root? = null    beforeEach {        kvapp = Root("kvapp", containerType = ContainerType.NONE, addRow = false)    }    test("generic button") {        kvapp!!.foButton("Button")        assertEqualsHtml("""...""")    }    test("primary button") {        val btn = kvapp!!.foButton("Button") { primary = true }        assertEqualsHtml("""...""")        btn.primary = false        delay(200)        assertEqualsHtml("""...""")    }})

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

Если внимательно присмотреться, то можно заметить, что во втором тесте есть delay() из корутин и при этом нигде нет модификатора suspend.

А он есть:

Это пока что всё, что я могу рассказать про kotest. Я ещё буду развивать библиотеку, и когда она будет готова, я анонсирую её выход в дискорде Fomantic и слаке Kotlin/kvision. И параллельно буду познавать kotest.

Если вам этого было мало, то приношу извинения: я хотел ещё здесь показать вывод ./gradlew --debug browserTest, но подготовка этого материала и так достаточно затянулась из-за появления личной жизни, так что если интересно -- созерцайте отладочные логи Gradle сами.

Ну и что? Ну и ничего. Кушайте Kotlin/JS, запивайте kotest-ом и берегите себя.

Подробнее..

Перевод Тестирование назад к основам PuppeteerMocha Совершенствуйте код с помощью тестового покрытия

09.07.2020 20:08:37 | Автор: admin

Перевод статьи подготовлен в преддверии старта курса Автоматизация тестирования на JavaScript.




Меня несколько раз спрашивали о разнице между инженером по обеспечению качества (QA Quality Assurance) и тестером (QC Quality Control), и я понял, что даже если люди, разрабатывающие программное обеспечение, путают такие простые понятия, то что уже говорить про остальных? Но по большому счету, вы понимаете, что им все равно, и это их право!


Людям просто нужны качественные продукты! Менеджеры хотят иметь программное обеспечение без багов [1] наилучшего качества, пользователям нужны качественные приложения, а этого можно достигнуть путем неустанного тестирования [2]. Но что это значит? Что такое качество (Quality)? Все дело в удовлетворении настолько тривиально! Мера качества это счастье конечных пользователей! Достижение этой абстракции становится настоящим испытанием для команды [3].


При разработке любого продукта, особенно программного обеспечения без тестирования, мы сталкиваемся с рисками [4], а они, с большой долей вероятности, сопряжены с потерей средств [5]. Так что, если вы не большой фанат тестирования, но для вас важно качество, то я предлагаю вам подумать еще раз и дать процессам обеспечения качества делать свое дело. Это относится ко всем членам команды и особенно к инженерам, которые имеют отношение к тест дизайну [6] инженерам по обеспечению качества.


Таким образом, посыл, что человек должен быть удовлетворен качественным приложением, подкрепляется подходами, уже сформировавшимися в лучшие практики [7]. Однажды занявшись тестированием, вы узнаете о существовании различных типов тестирования [8] и документации по тестированию (например, план тестирования). Затем вы сможете обрабатывать тест кейсы [9] на основе разных техник тест дизайна [10]. В конце концов, вы откроете для себя автоматизацию и к тому времени поймете разницу между QA и QC [11].


Глоссарий:


  1. несоответствие между фактическим и ожидаемым поведением
  2. полной проверки продукта путем верификации, валидации на основе спецификаций для обнаружения проблем
  3. люди, которые разрабатывают приложения, такие как дизайнеры, программисты, тестировщики
  4. (а) возможность негативного исхода (в соответствии с Кембриджским словарем);
    (b) действия в надежде на позитивных исход (в соответствии со словарем Google)
  5. (a) 125 миллионов пробников почвы Марса, потерянных из-за простой математической ошибки
    (b) много человеческих ошибок, приведших к крушению Boeing 737 Max
  6. пирамида тестов, фреймворк для разработки набора тестов, который оптимизирует скорость выполнения тестов с уверенностью, что ваше приложение работает должным образом
  7. семь принципов тестирования на International Software Testing Qualifications Board
  8. различные типы тестирования программного обеспечения от Atlassian
  9. этапы воспроизведения с ожидаемыми результатами в соответствии с требованиями приложения
  10. общие техники тестирования, которые вы должны знать
  11. ищите ответ здесь

Книги:


  • Тестирование Дот Ком, или Пособие по жестокому обращению с багами в интернет-стартапах, Савин Р. 2007
  • Continuous delivery. Практика непрерывных апдейтов, Эберхард В. 2017
  • Software Testing Automation Tips, Алпаев Г. 2017

[Puppeteer][Mocha] Проапгрейдите свой код с помощью тестового покрытия.


Пирамида тестов


С момента релиза Puppeteer сквозное тестирование (end-2-end) стало быстрым и надежным способом тестирования функционала. Большинство вещей, которые вы можете сделать вручную в браузере, можно сделать с помощью Puppeteer. Более того, Chrome без графики снижает нагрузку на производительность, а нативный доступ к протоколу DevTools делает Puppeteer просто потрясающим инструментом. Представьте, что каждый раз, когда мы разрабатываем интерфейс, мы просто проверяем окончательный вид в браузере без TDD мы сталкиваемся с мороженкой-антипатерном пирамиды тестов.



Сверху вниз: Ручные тесты / Автоматизированные GUI-тесты / Интеграционные тесты / Юнит-тесты


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


Настройка


У меня есть полная пошаговая README.md инструкция, основанная на простом проекте, который я форкнул и снабдил его многофункциональным тестовым апгрейдом в целях демонстрации (или чтобы выпендриться). Итак, если у вас есть еще один, пожалуйста:


1) Установите зависимости в корне


npm i puppeteer mocha puppeteer-to-istanbul nyc -D

2) Предоставьте свой экземпляр на конечной точке (мое простое решение для *.html http-сервер)


3) Создайте каталог test и заполните {yourFeature}_test.js следующим подходящим шаблоном (заполните хуки before и after), попробуйте расширить его с помощью специфичных для проекта селекторов и поведений:


const puppeteer = require('puppeteer');const pti = require('puppeteer-to-istanbul');const assert = require('assert');/** * ./test/script_test.js * @name Feature testing * @desc Create Chrome instance and interact with page. */let browser;let page;describe('Feature one...', () => {    before(async () => {        // Создание экземпляра браузера        browser = await puppeteer.launch()        page = await browser.newPage()        await page.setViewport({ width: 1280, height: 800 });        // Подключите покрытие JavaScript и CSS        await Promise.all([            page.coverage.startJSCoverage(),            page.coverage.startCSSCoverage()          ]);        // Конечная точка для эмуляции изолированного окружения        await page.goto('http://localhost:8080', { waitUntil: 'networkidle2' });    });    // Первый тестовый набор    describe('Visual regress', () => {        it('title contain `Some Title`', async () => {            // Настройка            let expected = 'Some Title';            // Выполнение            let title = await page.title();            // Проверка            assert.equal(title, expected);        }).timeout(50000);    });    // Второй тестовый набор    describe('E2E testing', () => {        it('Some button clickable', async () => {            // Настройка            let expected = true;            let expectedCssLocator = '#someIdSelector';            let actual;            // Выполнение            let actualPromise = await page.waitForSelector(expectedCssLocator);            if (actualPromise != null) {                await page.click(expectedCssLocator);                actual = true;            }            else                actual = false;            // Проверка            assert.equal(actual, expected);        }).timeout(50000);    // Сохранить покрытие и закрыть контекст браузера    after(async () => {        // Отключить покрытие JavaScript и CSS        const jsCoverage = await page.coverage.stopJSCoverage();        await page.coverage.stopCSSCoverage();        let totalBytes = 0;        let usedBytes = 0;        const coverage = [...jsCoverage];        for (const entry of coverage) {            totalBytes += entry.text.length;            console.log(`js fileName covered: ${entry.url}`);            for (const range of entry.ranges)                usedBytes += range.end - range.start - 1;        }        // вывести в лог исходное байтовое покрытие        console.log(`Bytes used: ${usedBytes / totalBytes * 100}%`);        pti.write(jsCoverage);        // Закрыть экземпляр браузера        await browser.close();    });});

Выполнение


  1. Запустите описанный выше тест для сценариев в конечной точке с помощью команды mocha
  2. Получите покрытие, собранное при выполнении теста с nyc report.
  3. Я предлагаю вам использовать ваш package.json с помощью следующих сценариев, что делает его очень простым для выполнения задач, таких как npm test или npm run cover

 "scripts": {    "pretest": "rm -rf coverage && rm -rf .nyc_output",    "test": "mocha --timeout 5000 test/**/*_test.js",    "server": "http-server ./public",    "coverage": "nyc report --reporter=html"  },

Покрытие


Мой проект покрыт на приблизительно 62%



Мы можем получить отчет в виде html и посмотреть поближе.



Вы можете видеть, что Branches и Functions покрыты на 100%. В то время как я тестировал функцию покрытия Puppeteer (типа Coverage devTool) я создал этот багрепорт.



[Bug] Неверные ветви попадают в статистику покрытия #22
Размещено storenth 27 декабря 2018
Когда готов nyc report --reporter=htm, я пытался посмотреть на ./coverage/index.html и обнаружил большой дефект в числе покрытия Branch, оно всегда равно 100%. Чтобы изучить эту проблему, я предлагаю клонировать этот простой репозиторий для локального воспроизведения.


Холивар Unit против E2E


Как кто-то, у кого достаточно страсти к исследованиям, чтобы не завыть от скуки за этим процессом, вот что я вам скажу: нам нужно уделять больше внимания средам модульного тестирования, таким как Mocha, для написания от Unit до Acceptance тестов, нежели самим писать их. Я думаю, не важно, какой тест вы пишете, если ваша кодовая база покрыта. Времена изменились. Теперь, с доступным функционалом покрытия, другие инструменты, такие как матрица прослеживаемости в качестве меры качества, выглядят ужасно, потому что заинтересованные стороны все еще должны полагаться на слова тестера.


Внесите свой вклад


Я настоятельно рекомендую уделить некоторое время и ознакомиться с моим github-working-draft проектом прежде, чем вы наломаете дров. Я буду признателен за любое сотрудничество и обратную связь. Не стесняйтесь обращаться ко мне с любыми вопросами.




Узнать подробнее о курсе Автоматизация тестирования на JavaScript



Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru