コードが断片化しやすくテストがしづらい

横瀬明仁氏:それでは、Django管理サイトのカスタマイズで苦労するポイント4「コードが断片化しやすくテストがしづらい」という点についてです。

まずは、このadmin.pyのコードを見てみてください。ModelAdminを継承して、クラス変数をオーバーライドしたり、メソッドを追加したりしていますが、例えばこれをカバレッジ100パーセントにしても、もうまったく意味がないような気がしませんか?

このように、AdminSiteやModelAdminを継承したクラスは、断片的なコードしかないので、どのようなテストをすればよいかが悩みどころだと思います。最後のポイント4では、Django管理サイトの効率的なテストについて解説していきたいと思います。

解決策1:lxmlを使ってテストをする

解決策としては2つあります。どちらもパッケージを使って効率的なテストをしていくアプローチです。

まず1番目。lxmlを使ってテストをしていくパターンです。lxmlは、HTMLやXMLを解析するためのライブラリです。通常のViewのテストでは、テストクライアントとselfのclientを使って、レスポンスを取得してテストをすると思います。ただ、ステータスコードなどは検証できても、画面項目の細かな検証までは難しいと思います。

そこで、lxmlパッケージを使います。responseのrendered_contentという属性が、レンダリング後のHTML文字列になっています。それを使って、lxmlというライブラリでパースすれば、画面項目を検証が可能になります。

具体的なコードはあとで実際のコードを見てもらったほうがいいと思います。このテストコードでは、Bookモデルの一覧画面の検証をしているイメージです。setUpでテストレコードを作成して、まずtestメソッドの中でログインします。モデル一覧画面を表示するためのリクエストを実行して、レスポンスを受け取っています。

ChangeListPageがモデル一覧の画面部品をlxmlでパースして、HTMLエレメントオブジェクトを返してくれるように作った自作のクラスです。これのイニシャライザにresponseのrendered_contentを渡して、あとは要素をそれぞれチェックする流れになっています。TestCaseクラスをオーバーライドしているので、Djangoのtestコマンドで実行できるテストになっています。

これが先ほど説明したChangeListPageの実装ですが、イニシャライザでresponseのrendered_contentを受け取ってパースします。そして、それぞれのプロパティで画面のHTMLエレメントオブジェクトを返すような仕組みになっています。

ここでは、lxmlでパースして画面項目を検証することで、効率的で効果的なテストができるという雰囲気だけをつかんでもらえればOKだと思います。

解決策2:Seleniumでブラウザーテスト

それでは2番目、Seleniumのブラウザーテストのお話です。Seleniumは、プログラムからブラウザーを操作するためのツールです。実際にブラウザーにレンダリングされたHTMLを操作するので、よりユーザーに近いテストができます。

Seleniumには、ブラウザーのAPIを利用するためのWebDriverインターフェイスと、主要なブラウザー向けの実装クラスが含まれていますが、Chromeを利用する場合はChromeDriverが別途必要になります。

Pythonの場合は、Seleniumパッケージです。ChromeDriverを使う場合はchromedriver-binaryパッケージが利用できます。chromedriver-binaryパッケージは、ローカルやサーバーで使うChromeと、一応同じバージョンのパッケージをインストールしておくのが無難かなと思います。

次に、Django組み込みのTestCaseクラスについて説明します。(スライドを指して)Django組み込みのTestCaseクラスの継承関係を図示したものですが、これがDjangoのデータベースに関係するテストをする時によく使うTestCaseクラスです。これ以外にもSeleniumをテストする時には、StaticLiveServerTestCaseをよく利用します。

Django管理サイトの場合は、特にDjangoは組み込みでAdminSeleniumTestCaseがデフォルトで用意されています。setUpClassでは、WebDriverインスタンス、テストメソッドからはselfのseleniumでアクセスを作成して、さらにWebサーバーも起動してくれます。

(スライドを指して)実際のコードはこんな感じです。これがAdminSeleniumTestCaseを継承したテストコードの例です。クラスメソッドのcreate_webdriverをオーバーライドしているのは、ChromeDriverをインスタンス化して、ヘッドレスモードで立ち上げるような仕組みにするために、create_webdriverをオーバーライドしています。

その続きは、Bookモデルの一連のCRUD操作をするためのテストメソッドをイメージして作っています。まずadmin_loginは、親クラスのAdminSeleniumTestCaseに作られているメソッドです。ログインすると、self.selenium.save_screenshotがスクリーンショットを撮ってくれます。

これはキャプチャー画像が撮れるので、テスト後に目検でこの画像をチェックすることで、実際にCSSがどのように反映されているのかを、目測でもチェックできます。

モデル一覧画面でキャプチャーして、「本を追加」ボタンを押して、モデル追加画面をゴニョゴニョしたあと、最後に「保存」ボタンを押すという流れです。ずらっと一連の動きを実行しつつ、キャプチャーを撮っていくことができます。

ポイント4のまとめです。「コードが断片化しやすくテストがしづらい」という困りごとに対しては、生成されるHTMLをlxmlでパースして画面部品を検証する。そして状況に応じて、Seleniumのブラウザテストを追加することで対応します。このSeleniumのテストは実行時間が長くなりがちなので、対応には注意してください。

日本語の情報は『現場で使えるDjango管理サイトのつくり方』で

カスタマイズするにあたっての再度の苦労ポイントとして「日本語の情報が少ない」という問題があります。最近、Djangoの日本語書籍がどんどん増えてきて、特に2021年7月19日に芝田(将)さんが出した『実践Django』は、超期待の本です。とはいえ、まだまだDjango管理サイトについては情報が少ないのは確かです。なにかいい本がないだろうかと思っている方もいるかもしれません。

そこで、はい。ドン。『現場で使えるDjango管理サイトのつくり方』という本があります。実は私が書きました。Django管理サイトだけに特化した、ニッチでオンリーワンな、152ページの狂気じみた本です。

というわけで、Django管理サイトのまとまった日本語情報がない問題については、『現場で使えるDjango管理サイトのつくり方』を現場に1冊置きましょうということで完全に解決しました。

Django管理サイトのカスタマイズにおける5つの悩みとその解決策まとめ

それでは全体のまとめです。このトークでは、Django管理サイトをカスタマイズするにあたって苦労するポイントと、その解決策を話しました。

そして、5つのポイント5つについては、それぞれ全体構造を把握しよう。テンプレートの修正には、テンプレートの優先順位と継承を使ってカスタマイズしよう。

画面のスタイルを変えるのが大変という問題については、CSSファイルを追加する仕組みを理解しよう。コードが断片化しやすくテストがしづらいという点については、lxmlやSeleniumを使っていきましょう。そして日本語情報については、すばらしい本があるのでぜひ読んでみましょうということで、このトークを終わりにしたいと思います。

ご清聴ありがとうございました。