パステル色な日々

気ままに綴るブログ

Salesforce大切なことはドキュメントに詰まってる

自分が初めてSalesforce開発で必要となった知識の参照元を並べてみます。

Lightning Componentの初期化時に何かしたい

Salesforce Developers

aura:attributeに入れる型って何があかんの?

Salesforce Developers

既存のJSライブラリを使いたいんだけど

Salesforce Developers

Lightning ComponentをLightningページで利用するための準備

Salesforce Developers

私のドメインって何?

Lightning ComponentをLightning アプリケーションビルダーで利用する時に必須となる

ヘルプ | トレーニング | Salesforce

何か参考にできるプロジェクトないの?

DreamHouse JP サンプルアプリケーション | Salesforce Developers

SOQLでリレーションクエリ作りたい

Salesforce Developers

sObjectの関係が知りたい

ER図があるぞ

Salesforce Developers

JavaScriptでのイベントの捌き方

Salesforce Developers

条件分岐するマークアップ書けるの?

aura:if

Salesforce Developers

Lightning アプリケーションと Lightning アプリケーションビルダーって違うの?

違う。Lightning アプリケーションは作ってもリリースする方法がわからなかったのでLightning アプリケーションビルダーを使ってLightning アプリケーションを作った。

Lightning Design System を使用した Lightning アプリケーションの作成 | Salesforce

コンポーネント間でJavaScriptコードを共有したいんだけど

静的リソースとして読み込んで使うしかないんや

Salesforce Developers

Lightning Componentの型情報ってApexではどうなるの

Lightning Components & AuraEnabled method parameters: What’s working and what’s not

sObjectってなにさ

Salesforce Developers

SOQLでの操作を一通り覚えたいんやけど

DML を使用したレコードの操作 単元 | Salesforce Trailhead

package.xml の書き方教えて

Salesforce Developers

開発環境とステージング環境作りたいんだけど

Sandbox利用しよう。DevHubがオンになっている組織ならスクラッチ環境を開発で使って、ステージングはSandboxでやるといいと思います。

Sandbox の詳細 単元 | Salesforce Trailhead

Salesforce Developers

VisualforceってLightning Componentと比べてどうなの

Visualforce と Lightning Experience | Salesforce Trailhead

クイックアクション、アプリケーションビルダーではまだサポートしてなかった

Lightning Component quick action not appearing in available actions for Lightning App page - Salesforce Stack Exchange

カスタム項目を必須項目にしたら全てのレコードタイプで必須扱いになる

全てのページレイアウトから削除できないので困るときには必須項目とせずにページレイアウト条で必須項目にしよう

ヘルプ | トレーニング | Salesforce

Apexサーバ側コントローラからエラーをLightning Componentに伝えたい

Salesforce Developers

一つのレコードを返すSOQLクエリ

Salesforce Developers

Apexのプリミティブ型には何があるの

Salesforce Developers

組み込みの例外

Salesforce Developers

Date型とDatetime型を変換する際にはタイムゾーンを意識しよう

Date型のフォーマット - KayaMemo

毎月の最終日の日付が知りたい

apex - How do we get the last day of the month from a given date - Salesforce Stack Exchange

Cronジョブみたいなのないの

ある

Apex スケジューラを使用したジョブのスケジュール 単元 | Salesforce Trailhead

サーバサイドのアクションを呼び出したい

Salesforce Developers

マークアップ内で式使いたい

Salesforce Developers

データバックアップの指針が知りたい

ヘルプ | トレーニング | Salesforce

外部APIを呼び出して使うけど気をつけることあるかな

ある

Salesforce Developers

リリース方法

Salesforce Developers

変更セットを使用した Sandbox からのリリース 単元 | Salesforce Trailhead

Standard Pricebookがテストケースで見つからないんだけど

Standard pricebook is not found in test class - Salesforce Developer Community

まとめ

一通り自分が初めてSalesforce開発で必要となった知識の参照元を並べてみました。 自分の言葉が少なめなのは補足の必要が無いからです。エラーが起きてもルールの範囲内であることが多く思いもしない物は少ないです。こういうところにプロダクトとしての素晴らしさがにじみ出てますね。

Apexコードを書くのに必要な知識は何

Apex

Apexは強く型付けされたオブジェクト指向プログラミング言語です。構文はJavaライクであり、Force.com プラットフォームサーバで処理を描く時に使用します。Apex を使うと、ボタンクリック、関連レコードの更新などのほとんどのシステムイベントにビジネスロジックを追加できます。

Lightning ComponentがクライアントサイドでApexがサーバサイドと言う理解でも良いと思います。 Lightning Componentからのリクエストをサーバサイドで処理できるようになります。 Lightning Componentから $A キーワードを利用することでエンドポイントの呼び出しができるので調べてみてください。

SOQL (Salesforce Object Query Language)

Force.com プラットフォームのデータベース操作に利用するクエリ言語です。SQLのような構文で書く事ができますが両者は違うものです。例えばSOQLでは SELECT * が出来ません。Salesforceはマルチテナント環境にありますが、マルチテナント環境では全員が「データベースを共有」しているようなものであるため、 * などのワイルドカード文字を使用すると問題が発生するからです。

ガバナ制限

これがApexコード格上での曲者。実はApexにはマルチテナント環境ゆえの制限事項が幾つかあります。

実行ガバナと制限 - Salesforce Developers

実行プロセスが共有リソースを独占しないように制限しており、超えた場合は 処理できない実行時例外 がthrowされるようになっています。何度も updateinsert 命令を発行したりすると簡単にSOQLの制限に引っかかってしまいます。 他にもforループで上記クエリを発行するのは絶対にやめましょう。

Apex開発で押さえておきたいポイント - Qiita

SOQLの制限で困ったときには実行したいトランザクションがどこからどこまでなのか把握してクエリを削減することに勤めましょう。トランザクションが分割可能であれば分割しましょう。

Apexトリガ

Salesforce のレコードに対するイベント (挿入、更新、削除) の前または後に処理できるApexプログラムです。 やたら操作の多いSalesforce上の仕事を自動化する際に利用できます。 これもガバナ制限には注意が必要です。特にトリガが原因で他のトリガを誘発してしまうことがあります。 例えば Opportunity のトリガは OpportunityLineItem の変更にも反応してしまうので、 OpportunityLineItem のトリガを作る際には Opportunity のトリガ内容にも注意しなくてはいけません。 先に書いてしまいましたがトリガは一つのsObject(Opportunity...etc.)に関連付けます。

Apex開発で押さえておきたいポイント - Qiita

Salesforce Developers

Apex一括処理

どうしてもガバナ制限を超えるような大規模ジョブを行う必要がある時にはApex一括処理を利用しましょう。

Apex 一括処理の内部的なしくみは次のとおりです。Apex 一括処理を使用して 100 万件のレコードを処理するとします。一括処理クラスの実行ロジックは、処理するレコードのバッチごとに 1 回コールされます。一括処理クラスを呼び出すたびに、ジョブは Apex ジョブキューに置かれ、別個のトランザクションとして実行されます。この機能には 2 つの大きな利点があります。

  • トランザクションは新しいガバナ制限のセットで開始されるため、コードをガバナ実行制限内に抑えやすくなります。
  • いずれかのバッチが処理に失敗しても、他のすべての正常に処理された一括処理トランザクションロールバックされません。

Apex 一括処理の使用 単元 | Salesforce Trailhead

単体テスト

Salesforceのリリース時には変更セットという機能を使ってリリースするのですが、この際には必ず単体テストが実行されます。 失敗すればリリースできないですし、 コードカバレッジ も75%なければエラー扱いとなりリリースできません。 なので単体テストは必ず書きましょう。テストしやすいように疎結合なコードを書くことももちろん大切ですよ。

まとめ

かなり簡素に大切なことだけ書きました。 逆にもっと細かいけどメモとして残していた知識がいくつかあるのでそちらは別途記事にします。

Lightning Componentの開発は昨今のフロントエンドの開発と大差ないのか

Lightning Component

Lightning ComponentとはLightning Experience(LEX)上で動作するUIです。 従来のフロントエンド開発と同様にComponentはUIの部品を指しています。 従来はLEXのようなSPAではなく、もっと別のコンセプトがありました。 近年移行が進んでいますが、ユーザーはLEXとClassicを使い分けることが出来ます。 どうしても組織でClassicを使いたいという要件があってもLEXに移行する雰囲気がするのでLightning Component開発に踏み切りましょう。 僕は特にSalesforceのような業務アプリケーションにおいては使いやすさよりもできるか出来ないかのほうが重要視されると考えます。 であれば慣れ親しんだClassicでわざわざ作る理由はないのではないでしょうか。

Sandbox

SalesforceSaaSです。Lightning Componentを作成しても実行環境が利用中のSalesforce上だと開発中のデバッグには向かないですし、何よりユーザーに迷惑をかける可能性があります。

Sandboxは利用中のSalesforceのスナップショットです。 利用中のSalesforceのデータとメタデータをコピーして環境を作ることが出来ます。

ForceCodeを使った開発

割りと情報がなくて困ったLightning Componentの開発のやり方について説明します。 やり方はいろいろありますが、今回はSalesforceの開発者ツールとVSCodeを使って開発します。

Salesforceの開発者ツールからLigntning Componentを作成します。 開発はこの開発者ツール上で完結することも出来ます。 ですが、今回は慣れ親しんだエディタで開発して、Gitでバージョン管理することを考えて作成したComponentをローカルにダウンロードします。

VSCodeには予めForceCodeをインストールして、Salesforceと認証おきましょう。

$ mkdir -p my-proj/src
$ cd my-proj
$ touch src/package.xml

src ディレクトリの下に package.xml を作ったら

<?xml version="1.0" encoding="UTF-8"?>
<Package xmlns="http://soap.sforce.com/2006/04/metadata">
  <types>
    <members>HelloComonent</members>
    <name>AuraDefinitionBundle</name>
  </types>
</Package>

中身にダウンロードしたいComponentの名前を書いてしまいましょう。 後はRetrieve PackageをRetrieve by package.xmlで実行するとダウンロードできます。 アップロードもForceCodeから行えます。 Lightning Componentの作成だけ開発者ツールでやらないといけないのが煩わしいですね。 注意が必要なのは同期していないということです。アップロードとダウンロードを繰り返すので常に上書きされることに注意してください。

Salesforce Developer Experience (SFDX)

2018年10月から始まった新たな体験です。 Force.com プラットフォーム *1 のアプリケーションを従来の慣れ親しんだ方法に近い形 *2 で開発することができます。 Sandboxではなくスクラッチ組織を作ってすぐに壊すことのできる環境を用意できます。 普段の開発はスクラッチ組織で行い。Staging環境をSandboxとして利用する方法がよいと思います。 その他にもCLIによるスムーズな開発がサポートされており、開発者ツールを利用せず従来のスピード感で開発することが出来ます。 利用するには組織のProduction環境で設定をONにするだけです。

*1: Force.com プラットフォームはSalesforceを構築するための基盤、つまりPaaS のサービス

*2: バージョン管理やCI, CLIツールの利用を指しています

言語

Lightning Componentの開発言語はJavaScriptとHtml、CSSです。 ただし、Salesforceの独自のタグが存在しているので要所では学習が必要になります。 また、JavaScriptはES2015に完全に対応していないため、Objectリテラルに対するスプレッド構文が利用できないです。 キホンはES5での開発だと思ったほうが良いでしょう。

ライブラリ

昨今のフロントエンド開発では切っても切り離せない多数のライブラリがありますが、これは静的ファイルとしてSalesforceに登録することで利用できます。 webpackを使ったbundleではなくライブラリは別にダウンロードされて実行されます。 また、AngularやReactといったライブラリを使いたい方もいると思います。 実際やってみた系の記事を見つけることが出来ますが、Visualforceページを利用したものが見つかると思います。 Lightning ComponentとVisualforceページは異なります。 VisualforceページをLightning Componentの中で利用することができます。

さて、AngularやReactを使う理由はなんでしょうか。Lightning Componentの開発はサポートされており、出来上がるものに差がないのであれば亜流の方法を選択する必要はないと考えます。 独自の価値を生み出すのであれば、利用した方がいいと思いますが慣れないから使うのであれば避けたほうが良いでしょう。 冷静になってください。 実際僕も余りの開発のやりにくさによって衝動に駆られました。

Lightning Design System

Lightning Design SystemはSalesforceのDesign Systemです。 組み込みのLightning Componentにも利用されており、美麗で使いやすいComponentパーツを素早く利用できる仕組みが整っています。 カスタムComponentを作るときには大いに利用しましょう。

結論

SFDXのような取り組みがはじまるくらいには開発がしにくいです。 JavaScriptライブラリの中にはバンドルされて利用されることを前提としてものも少なくありません。 ES2015も満足に使えないため昨今のJavaScript開発に慣れ親しんだ方はヤキモキするでしょう。

一方Lightning Desing Systemは素晴らしいです。 状態に応じたComponentのUIのDOMが手軽に利用できるため見た目に悩むことは殆どありませんでした。

Salesforceを使った自動化はできるのか

ひょんなことからSalesforceを使ってバックエンド業務を自動化することになりました。これはその際に学んだことの記録です。同じくひょんなことからSalesforceを触ることになっちゃった人への道標になれば幸いです。

なぜ記録するのか

Salesforceは巨大なシステムです。そのため、Google検索で手がかりを得ようにも断片的なキーワードから過去のコンセプトに沿った知識を取得してしまい大きく回り道をする可能性があります。そこでLightningだけに焦点を当てて記録することで無関連のワードに惑わされることなく知識を得てもらうことを目的としています。

何を目指したか

例えば次のような要件があるとしましょう。

  • ユーザーが入力を行うUI (ページ) が必要である
  • ユーザーが入力したレコードは永続化される必要がある
  • レコードを使って外部サービスを呼び出し、処理を依頼する

ユーザーのインタラクションをきっかけにSalesforceでの業務を自動化することが目標です。
Salesforceではこの要件をどうやって最短で叶えることができるのでしょう。

Lightning

Salesforceが現在推し進めているコンセプトです。いくつかのサービスやツールをまとめて呼称しています。
この内、Salesforce上でアプリケーション開発をする際のコアテクノロジーとしてLightning Experience、Lightning ComponentやLightning App Builderがあります。

この概要を掴むことのできる最高の資料があります。

はじめようLightningコンポーネント開発 | Salesforce Developers

これをみるとLightningをコンセプトに据えたSalesforceの最新事情がわかります。

Lightning ComponentでUIを作る前に

ここまででSalesforce上でUIを作るにはLightning Componentを作れば良さそうということがわかりますが、作る前に既存のLightning Componentでなんとかならないのか確認をするべきです。
既存のComponentは下記ドキュメントにまとまっています。

Salesforce Developers - コンポーネントの参照

これらの素晴らしいComponentで叶えることができるのであればこれを使いましょう。また、既存の機能をそのまま使えば叶えられることだってあります。リストビューもその一つです。

ヘルプ | トレーニング | Salesforce

Lightning App Builderでページを作る

Salesforce上でページを簡単に作るにはLightning App Builderを使います。 ページはLightning Componentの組み合わせから成ります。公開するとSalesforce上のアプリケーションに紐付けることができます。

ApexコントローラーでLightning Componentのリクエストを処理する

Lightning ComponentはSalesforceのレコードを直接読み書きすることができません。そこでApexコントローラーに依頼します。ApexはJavaライクなDSLです。Salesforceからレコードを読み書きするにはSOQLという別のDSLを使います。

Salesforce Developers - Apex とは?

結論

Lightning Component、Lightning App Builderを使ってUIを作り、ApexでSalesforceからレコードを読み書きすることで自動化できそうです。 次回はより詳細なやり方と注意事項についてまとめてみようと思います。

Pythonでデータ分析する準備をし〼。(Numpy激闘編)

データ分析前の準備体操。

Pythonを使うデータ分析

普段データ分析といってもいきなりPythonを使うことはしません
大抵はSQLにより取得できるデータをスプレッドシートへ吐き出し、datastudioを使って可視化することで顧客には十分な価値が提供できます
しかし、精度の向上や独自の仮説構築から検証に至るような過程でPythonを使ったデータ分析が必要になってきます
ちょうど僕も必要になるフェーズになったので今回はまずPython入門としてNumpyの学習に努めて見ます

NumPy入門

  • ベクトル生成
  • 行列生成
  • ベクトルから行列の生成
  • 行列のサイズ評価
  • 行列の演算
  • 配列の操作
    • スライシング
    • インデクシング
    • ブロードキャスティング

行列の転置を忘れていたので思い出す。行列の掛け算もわかりませんでした
学生時代に習ったことを忘れていたのがかなりショック...
面白かったのはNumpyをつかって生成した行列を掛ける際に dot という関数を使うのですが、これを使わずに掛け算を試みると対応したi行j列をかけ合わせたものになります。
おそらくこれは行列に行列を掛けるブロードキャスティングではないでしょうか

>>> a
array([[1, 2],
       [3, 4]])
>>> b
array([[5, 6],
       [7, 8]])
>>> a*b
array([[ 5, 12],
       [21, 32]])

スライシングをした際にインデックスが2以上2未満の指定で呼び出してみるとからの配列が返ってくるような感じです

>>> a
array([[1, 2],
       [3, 4]])
>>> a[0:2]
array([[1, 2],
       [3, 4]])
>>> a[2:2]
array([], shape=(0, 2), dtype=int64)

ところが存在しない行を取り出そうとすると、エラー扱いとなります

>>> a
array([[1, 2],
       [3, 4]])
>>> a[1,:]
array([3, 4])
>>> a[2,:]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: index 2 is out of bounds for axis 0 with size 2

特に行列の結合結果が面白いですね。 c__r を使った結合結果がひと目では理解できませんでした。

>>> np.r_[np.arange(3),np.arange(3)]
array([0, 1, 2, 0, 1, 2])
>>> np.c_[np.arange(3),np.arange(3)]
array([[0, 0],
       [1, 1],
       [2, 2]])

r_ は行列を縦に結合するものであるはずにも関わらず。横に結合されたように見えます。
縦に結合されると6行1列の配列を期待します。
array([[0], [1], [2], [0], [1], [2]]) これの結果の簡略と考えられそうです。
であるのであれば1次元配列からその配列が3行1列なのか1行3列であるのか見分けがつきそうにないですね。

まずは、Numpyによる行列演算について見ていきました。
疑問については仲間に助けてもらいながら教えてもらいます。次回はSciPy。

関数型言語らしい書き方をElmのコードで知る

覚えたての関数型言語でコードを書いてみると何故か関数型っぽくないなーと感じることがしばしばあったので、どう考えて書いていけばよいのか調べてみました。ここでは書籍 関数型プログラミング実践入門 よりトップダウンに考える方法をElmで紹介しようと思います。

対象者

最近、Elmを始めたがいまいち関数型言語らしい書き方がわからない方

わかること

書籍で書かれているように詳しく過程は紹介しません。 それでは始めましょう。

ランレングス圧縮

今回の問題は書籍で紹介されているランレングス圧縮を扱います。 詳しい説明はWikipediaを見ていただくとして、ここでは具体例だけをお見せいたします。

AAABBCCCCAAA -> A3B2C4A3

この通り、ランレングス圧縮とはこのような符号化のことを言います。

命令形言語のエッセンスで解く

まずはこちらをご覧ください。

module RLE exposing (rle)


-- >>> rle ""
-- ""
-- >>> rle "A"
-- "A1"
-- >>> rle "AAABBCCCCAAA"
-- "A3B2C4A3"

rle : String -> String
rle rawString =
    case String.toList rawString of
        [] ->
            ""

        h :: t ->
            aux 1 h (String.fromList t) -- 最初の一文字を覚える


aux : Int -> Char -> String -> String
aux runLength prevChar tailString =
    case String.toList tailString of
        [] ->
            String.concat <| String.fromChar prevChar :: [ toString runLength ] -- 文字がなくなったらおしまい

        c :: s ->
            if c == prevChar then
                aux (runLength + 1) prevChar (String.fromList s) -- 同じ文字なら連長をカウントアップ
            else
                String.concat <|
                    String.fromChar prevChar
                        :: [ toString runLength, aux 1 c (String.fromList s) ] -- 違う文字なら新たに1からカウント

書籍で紹介されているものをElmで書いたものです。forループを再帰で解いた感じがします。 しかし、これだけではどこが関数型言語らしくないのかわかりません。

関数型言語のエッセンスで解く

それでは次に関数型言語のエッセンスで解いてみます。 コードからトップダウンに問題を考えるってこういうことかーと感じていただければと思います。 文字列から文字列への変換の型を満たす関数を考える前に中間構造の型を考えてみましょう。

module RLE exposing (rle)


rle : String -> String
rle =
    fromCharAndRunLength << toCharAndRunLength
    
    
-- >>> fromCharAndRunLength [("A", 3), ("B", 4)]
-- "A3B4"
fromCharAndRunLength : List (Char, Int) -> String
fromCharAndRunLength =
    cat << rls2strs


-- >>> rls2strs [("A", 3), ("B", 4)]
-- ["A3", "B4"]
rls2strs : List (Char, Int) -> List String
rls2strs rls =
    ["P1"]


-- >>> cat ["A3", "B4"]
-- "A3B4"
cat : List String -> String
cat strs =
    "P1"


-- >>> toCharAndRunLength "AAABBBB"
-- [("A", 3), ("B", 4)]
toCharAndRunLength : String -> List (Char, Int)
toCharAndRunLength s =
    [('P', 1)]

Elm Search

Elm Searchを使うと型から関数を見つけることが出来ます。 すると String.concat 関数が見つかるので cat を置き換えてみましょう。 hoogleではないものの、Elmにもこういうサービスが有ったんですね。

module RLE exposing (rle)

import List.Extra exposing (group)


rle : String -> String
rle =
    fromCharAndRunLength << toCharAndRunLength
    
    
-- >>> fromCharAndRunLength [("A", 3), ("B", 4)]
-- "A3B4"
fromCharAndRunLength : List (Char, Int) -> String
fromCharAndRunLength =
    String.concat << rls2strs


-- >>> rls2strs [("A", 3), ("B", 4)]
-- ["A3", "B4"]
rls2strs : List (Char, Int) -> List String
rls2strs =
    List.map rls2str
    
    
rls2str : (Char, Int) -> String
rls2str (c, n) =
    String.concat <| String.fromChar c :: [ toString n ]


-- >>> toCharAndRunLength "AAABBBB"
-- [("A", 3), ("B", 4)]
toCharAndRunLength : String -> List (Char, Int)
toCharAndRunLength =
    List.map toPair << group << String.toList


toPair : List Char -> (Char, Int)
toPair rls =
    case rls of
        h :: _ ->
            ( h, List.length rls )

        [] ->
            Debug.crash ""

String -> List String をElm Searchで検索しても見つかりませんが、 List a -> List (List a) で検索してみると List.Extra.group が見つかります。 HaskellではChar型のリストがString型と考えるみたいでして、Elmでも同様に考えてみます。

さて、続けて toPair を実装してみましたが、よく見てください Debug.crash が登場しています。ロジック上これが呼ばれることはないはずですが、ここではきっちりとプログラム実行中に停止しないようにしてみます。

module RLE exposing (rle)

import List.Extra exposing (group)


rle : String -> String
rle =
    String.concat << List.map (rls2str << toPair) << group << String.toList


rls2str : Maybe ( Char, Int ) -> String
rls2str rls =
    case rls of
        Just ( c, n ) ->
            String.concat <| String.fromChar c :: [ toString n ]

        Nothing ->
            ""


toPair : List Char -> Maybe ( Char, Int )
toPair str =
    case str of
        h :: _ ->
            Just ( h, List.length str )

        [] ->
            Nothing

fromCharAndRunLengthtoCharAndRunLength はもう不要なので削除します。これで完成です。

どのあたりが関数型言語らしいコードなのか

トップダウンに問題を考えていき、機械的に型を解決していくことで解決まで出来ました。 前者と見比べてみると高階関数と関数合成を多様していることがわかります。 これはボトムアップな思考のまま逐次処理を行うコーディングをしていると見られない関数型言語らしい記述らしいです。 高階関数トップダウンに問題を考える上で非常に相性が良いとされていました。

やってみたものの果たしてElmでこの解き方が推奨されるのかはわかりませんが、こういう書き方もできるというご紹介でした。

繰り返しになりますが、本投稿は書籍 関数型プログラミング実践入門に紹介されている考え方を踏襲してElmで実装しています。本書にはより詳しく考え方が載っているのでぜひご一読ください。

また、今回使用してコードは次のリポジトリで公開しております。

https://github.com/pastelInc/elm-run-length-encoding/blob/master/src/RLE.elm

Elmでもテストを書こう

Elmでテストを書きましょう。
この文章はElm2 Advent Calendar 2017の22日目の投稿です。 昨日は @cyclone_t さんの YouTube IFrame Player APIをElmから利用する でした。

対象者

Elmのテストに興味がある人が対象になります。
ある程度、他言語でユニットテストを書いたことがある方が望ましいです。

わかること

  • Elmのテスティングライブラリはどんなものか
  • ElmのテストをCLI上で行うにはどうするのか
  • 文字列の一致をテストするElmのテストコードの書き方

それでは始めましょう

Elmのテスティングライブラリはどんなものか

Elmには他の言語同様にテストをサポートするライブラリが用意されています。

elm-community/elm-test

suite : Test
suite =
    describe "The String module"
        [ describe "String.reverse" -- Nest as many descriptions as you like.
            [ test "has no effect on a palindrome" <|
                \_ ->
                    let
                        palindrome =
                            "hannah"
                    in
                        Expect.equal palindrome (String.reverse palindrome)

            -- Expect.equal is designed to be used in pipeline style, like this.
            , test "reverses a known string" <|
                \_ ->
                    "ABCDEFG"
                        |> String.reverse
                        |> Expect.equal "GFEDCBA"

            -- fuzz runs the test 100 times with randomly-generated inputs!
            , fuzz string "restores the original string if you run it again" <|
                \randomlyGeneratedString ->
                    randomlyGeneratedString
                        |> String.reverse
                        |> String.reverse
                        |> Expect.equal randomlyGeneratedString
            ]
        ]

elm-testがElmにおけるテスティングフレームワークです。テストコードに必要なモジュール一式が提供されています。特徴的なのはファジングを気軽に実行できることです。

ファジングとは、ソフトウェアの不具合(とくに脆弱性を意図することが多い)を発見するためのテスト手法の一つである。ファズ(英:fuzz)(予測不可能な入力データ)を与えることで意図的に例外を発生させ、その例外の挙動を確認するという方法を用いる。ファズテストと呼ばれることもある。 ファジング - Wikipedia

Fuzzモジュールはファジングを強力にサポートします。seedからランダムな入力データを自動で生成してテスト対象コードの入力とすることが可能です。

Fuzz - elm-test 4.2.0

String型の生成データを例に見ても "", "\n" のような罠となりそうな文字列も生成してくれます。入力を自前で用意しないといけない手間が省ける点でも利用をおすすめできます。

詳しくは Elm2 Advent Calendar で @jinjor さんが記事をかかれています。要チェックですよ!

[Elm] Fuzzer でテストデータを量産しよう

ElmのテストをCLI上で行うにはどうするのか

Elmのテストコードはブラウザ上で結果を確認する方法とコンソール上で確認する方法が存在します。CIの都合上、一般的にはコンソール上で確認するシーンが多いと思いますのでコンソール上で実行するためのテストランナーをご紹介します。

rtfeldman/node-test-runner

node-test-runnner はテストの実行をサポートするだけではなく、初期テスト環境を自動構築してくれます。

$ elm-test init
Created ./src
Created ./elm-package.json
Created ./tests/elm-package.json
Created ./tests/Example.elm
Created ./.gitignore
Starting downloads...

  ● eeue56/elm-lazy-list 1.0.0
  ● eeue56/elm-lazy 1.0.0
  ● eeue56/elm-shrink 1.0.0
  ● elm-community/elm-test 4.2.0
  ● elm-lang/html 2.0.0
  ● elm-lang/virtual-dom 2.0.4
  ● mgold/elm-random-pcg 5.0.2
  ● elm-lang/core 5.1.1

Packages configured successfully!

テストコードが書けたらテストを実行しましょう

$ elm-test
Success! Compiled 0 modules.                                        
Successfully generated /dev/null
Success! Compiled 1 module.                                         
Successfully generated ./elm-stuff/generated-code/elm-community/elm-test/elmTestOutput.js

elm-test 0.18.10
----------------

Running 1 test. To reproduce these results, run: elm-test --fuzz 100 --seed 96181054


TEST RUN INCOMPLETE because there is 1 TODO remaining

Duration: 242 ms
Passed:   0
Failed:   0
Todo:     1
↓ Example
◦ TODO: Implement our first test. See http://package.elm-lang.org/packages/elm-community/elm-test/latest for how to do this!

テスト結果に注目してください。Todoがあるのがわかると思います。これはテストコード内にtodoを設定しておくことができるAPIが提供されているためです。 それでは実際にテストコードを書いてみましょう

Elmのテストコードの書き方

elm-test init で作成された Example.elm を少し変更してみます。

module Example exposing (..)

import Expect exposing (Expectation)
import Fuzz exposing (Fuzzer, int, list, string)
import Test exposing (..)


suite : Test
suite =
    describe "Example Test Suite"
        [ test "should equal" <|
            \() ->
                description
                    |> Expect.equal "Elm is functional language."
        ]

description : String
description =
    "Elm is functional language."

これは文字列 Elm is functional language. の一致をテストしています。
describeTest モジュールが提供しています。これを利用するとテストの名前をつけることができます。ここで定義しておくとテスト失敗時にどのテストケースが失敗したのかがわかりやすくなるので設定をおすすめします。第二引数がリストになっているのは複数のテストケースを渡すことができるからです。テストケースは test で実行しています。第一引数でテストケースに名前をつけています。第二引数でテストの詳細を実装します。実際には関数の実行結果をテストすることになります。

elm-test 0.18.10
----------------

Running 1 test. To reproduce these results, run: elm-test --fuzz 100 --seed 1753188457

↓ Example
↓ Example Test Suite
✗ should equal

    "Elm is functional language."
    ╷
    │ Expect.equal
    ╵
    "Elm is functionall language."



TEST RUN FAILED

Duration: 261 ms
Passed:   0
Failed:   1

テストをわざと失敗させてみました。するとこのように失敗したテストの詳細がターミナル上に出力されます。

等価性を判定する Expect.equal の他にも様々なAPIが提供されています。

Expect - elm-test 4.2.0

より実践的なテストコードの書き方は node-test-runner のexampleコードが参考になります。

node-test-runner/TestsFailing.elm at master

このテストコードでは先に説明したFuzzモジュールを使ったテストも体系的に学ぶことができるので、 git clone コマンドを実行して是非手元で試してみてください。

まとめ

  • elm-testを使おう
  • node-test-runnerを使ってテストを実行してみよう
  • Expectモジュールが提供するAPIを使ってテストコードを書こう

明日は @zaneli@github さんの Elm + native moduleでドラッグ&ドロップしたファイルの情報を取得する です!