Js2PyとpytestでJavaScriptでのDOM操作をテストしてみた
JavaScriptを使ってDOMを操作し、
Webページに情報を追加するようなWebサービスに対して、
良い感じに単体テストを書けないかを考えてみました。
検討した方法では、
適用できないことも多いと思いますが、
適用できる場合には、テストの効率化に役立つと思うので、
メモとして残しておきます。
このエントリで使用している言語やライブラリとそれらのバージョンは次の通りです。
- Python 3.7.12
- pytest 7.1.2
- mock 4.0.3
- Js2Py 0.71
- Flask 2.1.3
想定するWebサービス
このエントリで、想定するWebサービスは、
数値を2つ渡すとその和を、id=resultのタグ配下に挿入するサービスです。
- 入力:
- a: 数値
- b: 数値
- 出力: id=resultのタグ配下にa+bの結果を挿入するJavaScript
Webサービスの作成
それではFlaskで、Webサービスを作っていきます。
作業ディレクトリと仮想環境を作り、Flaskをインストールします。
mkdir flask-jstest-study && cd $_
python -m venv venv
. venv/bin/activate
pip install Flask
main処理を実装します。
main.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def home():
return '<html><div id="result"/><script src="/add/1+2"></script></html>'
@app.route('/add/<int:a>+<int:b>')
def add(a, b):
result = a + b
return f"""
let div = document.getElementById('result');
div.appendChild(document.createTextNode('{a + b}'));
"""
Flaskアプリケーションを実行します。
export FLASK_APP=main.py
export FLASK_ENV=development
flask run
http://localhost:5000/
にアクセスすると、
1+2の計算結果の「3」が表示されることが確認できます。
テストの追加
Webサービスにテストを追加します。
テストに使用するライブラリをインストールします。
pip install pytest
pip install js2py
pip install mock
テストコードを実装します。
HTMLDocumentクラスでは、
xml.dom.minidomでgetElementByIdを使うために、
以下のブログのコードを拝借しています。
Python の xml.dom.minidom で GetElementById をするメモ | bunji square
https://bunji2.hatenablog.com/entry/2016/10/09/211046
test_main.py
import pytest
import mock
import js2py
import xml.dom.minidom
from xml.dom.minidom import Node
import main
class HtmlDocument(xml.dom.minidom.Document):
# getElementByIdを使うためのメソッドの書き換え
# see. https://bunji2.hatenablog.com/entry/2016/10/09/211046
def getElementById(self, id):
def _get_element_by_id_helper(parent, id):
for node in parent.childNodes:
if node.nodeType == Node.ELEMENT_NODE and \
node.getAttribute("id") == id:
return node
r = _get_element_by_id_helper(node, id)
if r:
return r
return None
return _get_element_by_id_helper(self.documentElement, id)
@pytest.mark.parametrize(
"a,b,expect_xml",
[
(1, 2, '<div id="result">3</div>'),
(1, -2, '<div id="result">-1</div>')
]
)
def test_add(a,b,expect_xml):
# HTMLのDOMを準備 <html><div id="result"></html>
doc = HtmlDocument()
doc.appendChild(doc.createElement("html"))
div = doc.createElement("div")
div.setAttribute("id", "result")
doc.documentElement.appendChild(div)
# JavaScriptのContext準備、documentにDOMを設定
context = js2py.EvalJs()
context.document = mock.MagicMock(wraps=doc)
# サービスの名処理呼び出し・結果のJavaScriptの実行
context.execute(main.add(a, b))
# 処理実行後のDOMの検証
div_result = context.document.getElementById("result")
assert div_result.toxml() == expect_xml
テストは、以下のように実行します。
pytest test_main.py
xml.dom.minidomが、HTMLのDOMの機能を持っているわけでは無いので、
document.writeができない、innerHTMLが使えないなど、
不足する部分があって適用しにくいケースも多いと思うので、参考程度に。
以上。