自動テストフレームワークとブラウザ自動操作ツールの違いについて

概要

E2Eのテスト自動化を行うにあたりツールの選定をしていたところ
「ブラウザ自動操作ツール(PuppeteerやPlaywrightなど)と自動テストフレームワーク(CodeceptJSやGaugeなど)の違いって何だ?」
となったので、自分なりにまとめてみる。

ブラウザ自動操作ツール

文字通りブラウザを自動で動かすツール。
有名どころのツールであれば、

  • Puppeteer
    ChromeFirefox をヘッドレス(CUIでブラウザを動かすこと)で操作することができるNode.js のライブラリ

  • Playwright
    Puppeteer の開発チームがMicrosoftで開発したNode.js のライブラリ

  • Selenium
    Edge などの複数のブラウザにも対応している大規模なツール

などがある。
それぞれのツールでPythonJavascript などの言語で書くことができる。

自動テストフレームワーク

上記のブラウザ自動操作ツールと組み合わせてBDD(振る舞い駆動開発)やATDD(受け入れテスト駆動開発)のテストを自動で行うためのフレームワーク
具体的なツールとしては

  • CodeceptJS
    テストの流れをプログラムのように書くことができるNode.js のライブラリ

書き方の一例

Feature('ToDo');

Scenario('create todo item', ({ I }) => {
  I.amOnPage('http://todomvc.com/examples/react/');
  I.dontSeeElement('.todo-count');
  I.fillField('What needs to be done?', 'Write a guide');
  I.pressKey('Enter');
  I.see('Write a guide', '.todo-list');
  I.see('1 item left', '.todo-count');
});

書き方の一例
自然言語で書く部分

# Search the internet

## Search Google
* Goto Google's home page
* Search for "Cup Cakes"

プログラムの部分

# 上記の「Search for "Cup Cakes"」で呼び出される
step("Search for <query>", (query) => {
  write(query);
  press("Enter");
});

また、自然言語は日本語にも対応している

zenn.dev

などがある。

CodeceptJS+Puppeteerでプルダウンの値を選ぶ

概要

CodeceptJS+Puppeteerでプルダウン形式の値を選ぶ

環境

  • CodeceptJS v3.2.0

  • Puppeteer v11.0.0

解決策

codecept.io

上記の通り、

I.selectOption('Choose Plan', 'Monthly');

のように第一引数に変数名、第二引数にプルダウン形式の選択肢の中にある値を選ぶ。

WindowsにFESSのインストール

概要

FESSをインストールしたので備忘録

環境

OS:Windows

準備

Javaの用意 

jdk.java.net

上記のサイトからOpenJDK 11.0.2 (build 11.0.2+9) のzipファイルをダウンロードして解凍。 f:id:seven_901:20211031112446p:plain

FESSの用意 

fess.codelibs.org

上記を参考にこちらからFESSの最新版のzipファイルをダウンロードして解凍。

Elasticsearchの用意 

FESS をインストールしたサイトでElasticsearchのバージョンを確認してから

www.elastic.co

公式サイトからElasticsearchのzipファイルをダウンロードして解凍。

インストール

JAVA_HOMEについて

fess.codelibs.org

公式でWindowsJAVA_HOMEのパスを設定しているが、

qiita.com

上記の記事を参考にFESSのfess.batファイルに

SET JAVA_HOME=JAVAのzipファイルを解凍したパス

を追記してJAVA_HOMEのパスをFESSを実行するときにはダウンロードしたJavaファイルを参照するようにした。

FESSとElasticsearchを連携するための準備

前準備

先にElasticsearchのJAVA_HOMEもダウンロードしたJavaファイルを参照するように設定。
/bin/elasticsearch.bat

ES_JAVA_HOME=JAVAのzipファイルを解凍したパス

を追記。

プラグインのインストール

fess.codelibs.org

上記を参考にプラグインのインストールやElasticsearch、FESSの設定ファイルを書いていく。

プラグインのインストールで引っかかる

qiita.com

プラグインのインストールが上手くいかないので、上記を参考にした。

起動

先にElasticsearchでbin¥elasticsearch.batを実行。
次にFESSでbin¥fess.batを実行して http://localhost:8080 にアクセス。

ナイーブベイズ分類器について

概要

ナイーブベイズ(単純ベイズ)について調べたので備忘録。
ここでは文書分類を題材に書きます。

説明

数式

一般的にナイーブベイズの数式は下記の通り。


P(category|word) \propto P(category) \prod_{i=1}^{n} P ( word_{i} | Category)
記号 説明
P(category|word)
対象とするカテゴリである確率
P(category)
対象とするカテゴリが含まれている確率
\prod_{i=1}^{n} P ( word_{i} | Category) 対象とするカテゴリに含まれる各単語の確率の総乗

一般的に分母については考えないことが多く「それらしい答えが出たらいいでしょ」ということらしい。(こういう考え方は最尤法と呼ばれる。)
また、前提として単語の出現確率は独立性がある(次に出てくる単語の確率に影響しない)とすることで上記の式が成り立つ。
例えば「人工」という単語があったときに次に「知能」という単語が出てくる確率と「芝生」という単語が出てくる確率は同じという考え。
この点がナイーブ(単純)ベイズと呼ばれる由縁だと思われる。

ゼロ頻度問題

上の数式の中で出てこない単語(未知語)があったときに単語の出現する確率は0になってしまう。
それを防ぐために加重スムージングというのがある。
この手法は単語の出現回数に1を足して、全文書の重複を除いた全単語数をカテゴリの単語数に加算する手法。

実際の問題も踏まえて

文書 単語 カテゴリ
今日はいい天気です。 今日、いい、天気 A
今日の午後から雨が降る予定なので、傘を持って行きます。 今日、午後、から、雨、降る、予定、なので、傘、持つ、行く A
夕飯はステーキです。 夕飯、ステーキ B
お昼はカレーでした。 昼、カレー B
明日の朝ごはんはおにぎりの予定です。 明日、朝ごはん、おにぎり、予定 B

上のような5件の文書があり、単語が上のようなとき

  • カテゴリAの文書は2件なのでP(category)は2/5

  • カテゴリBの文書は3件なのでP(category)は3/5

  • カテゴリAに出てくる単語は重複している単語も含めて「今日」「いい」「天気」「今日」「午後」「から」「雨」「降る」「予定」「なので」「傘」「持つ」「行く」の13単語

  • カテゴリBに出てくる単語は「夕飯」「ステーキ」「昼」「カレー」「明日」「朝ごはん」「おにぎり」「予定」の8単語

  • 全文書に出てくる単語数は重複した単語を除いて20単語

  • カテゴリAの単語の出現確率(P ( word | Category))は「今日」は 2 / 13、それ以外の単語は1 / 13

  • カテゴリAの出現確率に加重スムージングを適用すると「今日」は(2 + 1) / (13 + 20) = 3 / 33、それ以外の単語は(1 + 1) / (13 + 20) = 2 / 33、未知語の場合は1 / 33

  • カテゴリBの単語の出現確率(P ( word | Category))はそれぞれの単語の出現確率は1 / 8

  • カテゴリBの出現確率に加重スムージングを適用するとそれぞれの単語の出現確率は(1 + 1) / (8 + 20) = 2 / 28、未知語の場合は1 / 28

となる。
次に文書「お昼のご飯の予定は?」という文書が用意されて、単語が「昼」「ご飯」「予定」の5単語の場合、 カテゴリAは


P(category|word) = \frac{2}{5} × \frac{1}{33} × \frac{1}{33} × \frac{2}{33}  = 0.0000222545016

カテゴリBは


P(category|word) = \frac{3}{5} × \frac{2}{28} × \frac{1}{28} × \frac{2}{28}  = 0.0001091983032

よって「お昼のご飯の予定は?」はカテゴリBに分類される。

Golangの正規表現の処理速度

概要

Golang正規表現を扱うことになったので、ググったところgolangのサジェストワードに「遅い」というワードが出てきた。
なので、調査してみた。

Golang正規表現

実装

pkg.go.dev

上記のサイトはGolang正規表現を扱うときに使われるregexpパッケージの公式ドキュメント。
ドキュメントによる処理速度に関する記述では

The regexp implementation provided by this package is guaranteed to run in time linear in the size of the input. (This is a property not guaranteed by most open source implementations of regular expressions.)

と書かれており、ざっくり日本語訳すると
Golang正規表現の実装は入力サイズに対して線形時間で処理されることが保証されている」
と書かれている。

さらに、ドキュメントには

The syntax of the regular expressions accepted is the same general syntax used by Perl, Python, and other languages. More precisely, it is the syntax accepted by RE2 and described at https://golang.org/s/re2syntax, except for \C.

と書かれており、GolangregexpはRE2で実装されていることが分かる。

RE2

github.com

上記サイトはGoogleが提供しているRE2のオープンソース
こちらによると、RE2の処理速度は入力文字に対して、線形時間で処理されることが保証されていることが書かれている。
つまり、GolangregexpパッケージはRE2を元に実装されているので、入力文字数に対して線形時間で処理されるということらしい。

より詳しい話

swtch.com

上記のサイトはGolangの生みの親の一人であるRuss Coxによる正規表現の実装について書かれている。
サイトではイントロダクションでPerl 5.8.7とThompson NFAアルゴリズム(RE2で採用されているアルゴリズム)の処理速度の比較から始まっていた。

ざっくりとまとめると

  • Perl正規表現の入力文字と処理速度の関係は指数関数のグラフになるが 、Thompson NFAは線形関数のグラフになる。

  • 入力文字が29単語にマッチする正規表現を書いたときの処理時間はPerl 5.8.7は60秒かかるが、Thompson NFAは20秒ぐらいで済む。

まとめ

  1. Golang正規表現を扱うにはregexpパッケージを利用

  2. regexpはRE2を参考に作られているので、処理時間は入力される単語数に対して線形時間になる

  3. RE2はThompson NFAアルゴリズムを採用しているので、処理時間が線形時間になる

参考

naoyat.hatenablog.jp

medium.com

Golangでjsonを読み込む

概要

タイトル通りGolangjsonファイルを読み込む

実装

実装のサンプルには言語処理100本ノック 2015jawiki-country.jsonを利用。

パッケージ

import (
    "encoding/json"
    "bufio"
    "fmt"
    "os"
    "io"
)

パッケージはencoding/jsonjsonの処理を行う。
ファイル処理のためにosioを利用する。(io/ioutilGo 1.16より非推奨になったらしい。)

構造体

type Article struct {
    Text    string  `json: "text"`
    Title   string  `json: "title"`
}

Golangではjsonをデコードするために構造体を用意する必要がある。
各行のjson: "hoge"ではjsonのフィールド名を書く。
もし、自分で定義することが難しい場合には下記のサイトにjsonファイルを投げれば、構造体を自動的に作成してくれる。

mholt.github.io

jsonの中身が分かっていないあるいは、ネストが深くなってよく分からない場合には、 map[string]interface{}を使った方法がある。(ここでは書かない)

ファイル処理

f, err := os.Open("jawiki-country.json")
if err != nil {
    fmt.Println("error")
}

osパッケージでファイル処理。

jsonファイルの読み込み

// 1行ずつ処理
r := bufio.NewReader(f)

// JSONデコード
var jawiki []Article
    
for {
    b, err := r.ReadBytes('\n')
    if err == io.EOF {
        break
    }
    var article Article
    json.Unmarshal([]byte(b), &article)
    jawiki = append(jawiki, article)
}

今回の場合、jsonファイルの中身が特殊だったので、1行ずつファイル処理を行った。
json.Unmarshaljsonファイルのデコードを行い、第1引数には[]byte、第2引数にはstructである必要がある。
もし、普通のjsonファイルであれば、

if err := json.Unmarshal(bytes, &jawiki); err != nil {
       log.Fatal(err)
}

で読み込むことができる。

デコードしたファイルの中身の確認

for _, j := range jawiki {
    fmt.Println(j.Text, j.Title)
}

確認方法は単純でforで中身を確認することができる。

GolangでPythonのSet型を使う

概要

GolangPythonのSet型を使いたい。

解決策

github.com

上記のパッケージをダウンロードすればPythonのset型を利用できる。

ダウンロード

go get github.com/deckarep/golang-set

使い方

インポート

import (
    "github.com/deckarep/golang-set"
)

型宣言

str1Set := mapset.NewSet()
str2Set := mapset.NewSet()

追加

String型

str1Set.Add("Cooking")

和集合

str1Set.Union(str2Set)

積集合

str1Set.Intersect(str2Set)

差集合

str1Set.Difference(str2Set).Union(str2Set.Difference(str1Set))

特定の文字列が入っているかどうか

str1Set.Contains("Cooking")