Статьи

Создание визуального модульного теста для React

Создание простого приложения для обучения понятиям выигрыша в покер с использованием функционального программирования. Этот проект использует визуальные элементы и модульное тестирование как способ «геймификации» процесса обучения. Несколько простых компонентов в сочетании с мощной и простой в использовании сеткой пользовательского интерфейса Kendo создают осмысленное отображение тестов с данными.

Как разработчик Advocate for Progress я провожу часть своего времени преподаванием в виде семинаров, конференций и примеров. Я преподавал методы функционального программирования для разработчиков на C # в течение нескольких лет, а недавно начал адаптировать этот контент, чтобы также включать JavaScript.

Контент, который я разработал, включает в себя:

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

«Почему бы просто не использовать XYZ Framework?»

С таким количеством отличных платформ для модульного тестирования, доступных для React, было нелегко создать еще один метод тестирования кода. Тем не менее, я действительно хотел сделать что-то особенное для предмета, которое не вводило бы другую концепцию, такую ​​как инфраструктура модульного тестирования, которая имеет свою собственную кривую обучения. Вместо этого я хотел что-то, что позволило бы студентам сосредоточиться на коде, используемом в демоверсии.

Соединение концепции с визуальным

В своем контенте я стараюсь создавать визуальные элементы, которые помогают студентам соединиться с представляемой идеей. Я считаю, что игральные карты связаны друг с другом, поскольку они представляют собой объект реального мира. Еще одним удобным свойством игральных карт является то, что они отображают свои значения, что делает их легко различимыми.

Целью теста визуального блока является отображение руки с изображениями игральных карт вместе с их соответствующими значениями. В этом окне я добавлю желаемый результат покерного рейтинга, описание и значение пройденного / неудачного прохождения. Конечным результатом будет проект, который пользователи могут открыть с помощью StackBlitz и начать писать код для освещения модульных тестов. Все, что им нужно для начала работы, будет готово к использованию.

Детали компонента

Для создания компонента Visual Unit Test я использовал React. Я выбрал React, потому что тема функционального программирования часто связана со стилем разработки React. Кроме того, StackBlitz предлагает простые шаблоны для начала работы с React, предоставляя результаты в режиме реального времени через «Горячую перезагрузку по мере ввода», и это обеспечивает механизм для немедленной обратной связи с учеником.

Сам компонент Visual Unit Test состоит из сетки Kendo UI для отображения изображений игральных карт и соответствующих значений, используемых в тесте. Сопровождение сетки — это строка состояния с описанием того, что тестируется (т. Е. «Две карты одного значения»). Строка состояния также показывает результат теста по цвету и тексту. Чтобы помочь учащимся определить, что происходит в тесте, выходное значение отображается под строкой состояния.

Сетка Kendo UI

Сетка Kendo UI связывается практически с любым источником данных и предлагает широкий набор функций, которые обеспечивают интуитивно понятный дисплей для взаимодействия с данными. Привязка данных к сетке так же проста, как установка свойства данных. Для модульных тестов я использую локальные данные игральных карт, хранящиеся в виде файла JSON, который загружается в состояние компонента. Как только свойство данных установлено, сетка автоматически генерирует столбцы на основе объектов данных.

// test-data.json

"royalFlush": [
  {
    "suit": "HEARTS",
    "value": "QUEEN",
    "numValue": 12,
    "image": "https://deckofcardsapi.com/static/img/QH.png",
    "code": "QH"
  },
  // ... more cards
]
// VisualUnitTest.js

<Grid data={this.state.dataDisplay}>
</Grid>

Столбцы могут быть заданы явно, что обеспечивает полную настройку и дальнейшее улучшение отображения сетки. Одной из ключевых особенностей этого сценария было отображение настраиваемого шаблона сетки. Использование шаблона ячейки означало, что я мог легко отображать изображения игральных карт в столбце сетки. Настройка шаблона столбца для сетки пользовательского интерфейса Kendo требует двух простых шагов. Сначала создается компонент ячейки сетки, который наследуется от  GridCell . Затем компонент связывается со cellсвойством столбца, в котором он будет использоваться.

// VisualUnitTest.js

<Grid data={this.state.dataDisplay}>
  <GridColumn field="image" title="Card" width="90" cell={ImageCell} />
  <GridColumn field="suit" title="Suit" />
  <GridColumn field="numValue" title="# Value" />
  <GridColumn field="code" title="Code" />
</Grid>

class ImageCell extends GridCell {
  render() {
    return (
      <td>
        <img src={this.props.dataItem[this.props.field]} width="60px" />
      </td>
    );
  }
}

Кроме того, включена сортировка по нескольким столбцам, чтобы учащиеся могли настраивать свой вид данных. Подробности и демонстрации о сортировке и дополнительных функциях сетки Kendo UI можно найти на веб-сайте Kendo UI .

Строка состояния

Отображение данных — это только одна часть визуализации, и для завершения процесса был необходим визуальный элемент прохождения / неудачи. Чтобы завершить визуальный тестовый модуль, я включил компонент статистики. Строка состояния — это простой компонент, который использует условные стили CSS для отображения состояния модульного теста. В строке состояния находится описание теста, текст "pass"или "fail"и буквенное строковое представление фактического тестируемого значения.

<Grid .../>
<PassFail description={this.props.description} value={this.state.pass} />
<small>Output was: {JSON.stringify(this.props.actual)}</small>

class PassFail extends Component {
  constructor(props) {
    super(props);
    this.state = {passFailText: this.props.value  ? "pass" : "fail"}
  }
  render() {
    return (
      <div className={this.state.passFailText  + " output"}>
        <p>{this.props.description}
          <span>{this.state.passFailText}</span>
        </p>
      </div>
    );
  }
}
.fail {
  background-color:$fail-color; // #D24D57
}

.pass {
  background-color:$pass-color; // #6B9362
}

Комбинация сетки и строки состояния завершает компонент визуального тестирования. Для завершения работы приложения компоненту предоставлены тестовые данные и тестовая конфигурация.

Положить его вместе

The visual unit testing app is driven by a single file which wires everything up. The test.js file marries the test data, units under test, with an array of test configurations. In this scenario, I’m testing two public functions that the student is responsible for creating: getHandRank(), which scores every poker hand available in a game of five card poker, and getHighCard(), a function which must return the highest card object in a hand.

// test.js

// test data
import data from './test-data.json';
// Unit under test
import { getHandRank, getHighCard } from '../components/Scoring.js';

// An array of test configurations.
export { tests };

const tests = [
  {
    id: 11,
    name: "1. Royal Flush",
    data: data.royalFlush,
    actual: getHandRank(data.royalFlush),
    expected: "RoyalFlush",
    description: "A straight flush including ace, king, queen, jack, and ten all in the same suit."
  },
  // ...more tests
]

A test configuration consists of the name, data, the actual value (the unit under test), the expected value and the description. The actual and expected values are used by the visual unit test component to determine if the test is pass/fail. Internally, the visual unit test component is performing a deep-equals against the two values to produce a «passing» value. Once a pass/fail is determined, the unit test will display the corresponding color, text, and output value.

// VisualUnitTest.js

import deepEqual from 'deep-equal';

export default class VisualUnitTest extends Component {
    this.state = {
      pass: deepEqual(this.props.expected, this.props.actual),
      dataDisplay: this.props.data,
      // ...
    };

// ...
}

To complete the app experience, the test configurations are iterated over the visual unit test component. Using a simple map operator, the component is initialized with the a test configuration and displayed.

import VisualUnitTest from './tests/VisualUnitTest';
// Unit test definitions.
import { tests } from './tests/tests.js';

class App extends Component {
  constructor() {
    super();
    this.state = { tests: tests };
  }

  render() {
    return (
      <div>
          {this.state.tests.map(test => (
              <VisualUnitTest key={test.id}
                id={test.id}
                name={test.name}
                data={test.data}
                actual={test.actual}
                expected={test.expected}
                description={test.description}
              />
          ))}
      </div>
    );
  }
}

The application is complete and students have a project they use to practice writing functional code to complete the tests and see a visualization. Students will open the scoring.js file and be prompted to complete the two functions (or units under test) to solve for the tests provided. As the code is written in scoring.js, the visuals will update in real-time, showing which tests are passing/failing:

export { getHighCard, getHandRank }

// Use this file to complete the following functions and
// solve all of the unit tests displayed to the right.
//
// Try a functional approach. Complete all of the unit
// tests without defining a single variable.

// Test 1b. Get the highest card.
// The card with the highest value in the hand. (Deep Equals, return the full card object).
const getHighCard = function (cards) {
  return;
};

// Tests 1a - 10
// Score all of the hands of poker.
const getHandRank = function(cards) {
  return; // return the string "HighCard" to complete the first test.
};

Conclusion

This project uses visual elements and unit testing as a way to «gamify» the learning process. A few simple components combined with the powerful and easy to use Kendo UI grid create an meaningful display of tests with data. Using StackBlitz and React provide the perfect platform for a teaching tool for JavaScript developers. I’m excited to see how students will interact with this idea in a workshop, meetup, or webinar setting.

If you’re interested in seeing the hour long version of the material, a recording is available on the Progress YouTube channel.

Try it Yourself

If you’re the hands on type try out the Poker scoring app in StackBlitz and take it for a spin. To see more of what the Kendo UI data grid and our other React compoents has to offer, start a 30 day free trial of Kendo UI.