Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
正規表現のテストカバレッジを測りたかった話
Search
yubessy
May 29, 2017
Programming
2.4k
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
正規表現のテストカバレッジを測りたかった話
※社内LT用につくったもの
yubessy
May 29, 2017
More Decks by yubessy
See All by yubessy
DDIA (Designing Data-Intensive Applications) はいいぞ
yubessy
0
1.6k
Introduction to CircleCI
yubessy
1
130
Docker Hands-on
yubessy
0
130
Resource Polymorphism
yubessy
0
310
不動点コンビネータ?
yubessy
0
330
とりあえず機械学習したかった
yubessy
0
350
Scala Native
yubessy
0
240
Type Erasure と Reflection のはなし
yubessy
1
490
量子暗号
yubessy
0
250
Other Decks in Programming
See All in Programming
Oxlintはいいぞ(続)
yug1224
1
480
Press start. Python's next generation.
willingc
PRO
3
260
Japan Community Day at Kubecon + CloudNativeCon Japan 2026: Learning Container Privilege Control by Building My Own Low-Level Container Runtime
ternbusty
1
170
AIに既存システムを理解させる技術 ~レガシーを見捨てないハーネスエンジニアリング入門~
ochtum
0
170
30年振りにコンパイラの定数整数除算を改善した
herumi
9
4.3k
PyConJP2026_wat_Python × Signal Processing: How to Draw Pictures with Sound Using Spectrogram Art
wat
0
580
komatsuna「分散システムにおけるバグ分析手法」
komatsunaqa
0
290
自動化したのに回らない テスト運用の壁―AI時代の品質責任と生産性
mfunaki
0
540
実装をデザインガイドラインに追従させるための取り組み / 260731-dip-mosh-design-system
dachi023
0
7.9k
思考垂れ流し開発 ~音声入力 × AIエージェント × 開発ハーネスによる試行錯誤~
npostring
0
420
AI Readyの正体はデータマネジメントだ メダリオン2.0の最前線
freee
PRO
0
430
承認済みなのに差戻しできてしまうバグ、型で潰せます
shinchit
0
120
Featured
See All Featured
How to audit for AI Accessibility on your Front & Back End
davetheseo
0
520
How to Get Subject Matter Experts Bought In and Actively Contributing to SEO & PR Initiatives.
livdayseo
0
180
The Psychology of Web Performance [Beyond Tellerrand 2023]
tammyeverts
49
3.5k
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
31
10k
Amusing Abliteration
ianozsvald
1
270
Marketing to machines
jonoalderson
1
5.7k
Ruling the World: When Life Gets Gamed
codingconduct
0
310
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
12
1.8k
A Modern Web Designer's Workflow
chriscoyier
698
190k
Scaling GitHub
holman
464
140k
The MySQL Ecosystem @ GitHub 2015
samlambert
251
13k
How Fast Is Fast Enough? [PerfNow 2025]
tammyeverts
3
830
Transcript
正規表現のテストカバレッジを 測りたかった話 @yubessy 0x64 物語 Reboot #03 " テスト"
正規表現書いてますか?
テスト書いてますか?
正規表現のテスト ↓ これをテストしたい /[a-z]+\.(co|ne)\.jp/ たぶんこんなかんじ assert("hoge.co.jp" =~ /[a-z]+\.(co|ne)\.jp/) assert("hoge.ne.jp" =~
/[a-z]+\.(co|ne)\.jp/)
やりたいこと 正規表現のテストカバレッジを測りたい /[a-z]+\.(co|ne)\.jp/ ↓ カバレッジが十分でない例 assert("hoge.co.jp" =~ /[a-z]+\.(co|ne)\.jp/) # "hoge.ne.jp"
は? こういうのを数値化したい
Q. それくらい見ればわかるやん?
A. (?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_ `{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\ x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0 -9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0 -9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3 }(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0 -9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\ [\x01-\x09\x0b\x0c\x0e-\x7f])+)\])
A. RFC5322 準拠の email adress (?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_ `{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\ x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0 -9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0 -9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3
}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0 -9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\ [\x01-\x09\x0b\x0c\x0e-\x7f])+)\]) https://emailregex.com/
どうやってカバレッジを測るか? 正規表現 = 状態機械 カバレッジ(C0) = 全てのパスを1 度以上通ったか ※C1 以上はとりあえず今回は無視
ちなみにさっきのやつ
ここで問題発生 大体の言語では正規表現はライブラリとして提供 -> 内部実装はラップされていて見えない -> 各パスを通った・ 通らないを知るすべがない -> 処理系に手を入れる
手頃な処理系を探す旅 PCRE やばい java.util.regex つらい Oniguruma / Onigumo でかい Rust
おしい( 僕の力では読めない) Golang <- !!!
Golang の正規表現エンジン NFA( 非決定性有限オー トマン) ベー ス アルゴリズムが理解しやすい 計算量が文字数に対して線形 平均的には遅い
Pure Go で実装 自分でも読める コー ド量が手頃( エンジン部分は数100 行) https://github.com/golang/go/tree/master/src/regexp
Golang の正規表現エンジン re := regexp.MustCompile("/[a-z]+\.(co|ne)\.jp/") re.MatchString("hoge.co.jp") 内部では 1. 正規表現の構文木を作成 (
syntax/parse.go ) 2. 構文木からプログラムを作成 ( syntax/compile.go ) 3. 文字列を入力としてプログラムを実行 ( exec.go )
正規表現プログラムの内部表現 プログラム = 文字を1コずつ処理する命令の列 syntax/prog.go type Prog struct { Inst
[]Inst ... } type Inst struct { Op InstOp // 命令の種類 Out uint32 // 成功時のジャンプ先の命令番号 Arg uint32 // 失敗時のジャンプ先の命令番号( など) Rune []rune // マッチする文字( 文字マッチ命令のみ) }
プログラムの内部表現 0: InstFail 0 0 [] // 失敗( 最初はスキップ) 1:
InstRune 2 0 ['a', 'z'] // 'a' - 'z' 2: InstAlt 1 3 [] // 命令1, 3 のいずれか 3: InstRune1 4 0 ['.'] // '.' 4: InstCapture 9 2 [] // グルー ピング開始 5: InstRune1 6 0 ['c'] // 'c' 6: InstRune1 10 0 ['o'] // 'o' 7: InstRune1 8 0 ['n'] // 'n' 8: InstRune1 10 0 ['e'] // 'e' 9: InstAlt 5 7 [] // 命令5, 7 のいずれか 10: InstCapture 11 3 [] // グルー ピング終了 11: InstRune1 12 0 ['.'] // '.' 12: InstRune1 13 0 ['j'] // 'j' 13: InstRune1 14 0 ['p'] // 'p' 14: InstMatch 0 0 [] // 成功
カバレッジの計算 やりたいこと = 正規表現のカバレッジを測る -> 各命令にテスト済みフラグを用意 type Inst struct {
Op InstOp Out uint32 Arg uint32 Rune []rune Flag bool // テスト済みフラグ <- new! }
カバレッジの計算 マッチの実行時に通った命令をマー キングしておく // 文字マッチ命令を1 つ実行する関数 (exec.go) func (m *machine)
step(...) { ... switch i.Op { ... case syntax.InstRune: add = i.MatchRune(c) case syntax.InstRune1: add = c == i.Rune[0] ... if add { i.Flag = true // マッチが成功したらフラグを立てる } ... }
カバレッジの計算 テストケー スを食わせてフラグの立った命令を数える func (re *Regexp) Coverage() (int, int) {
s, a := 0, 0 for _, x := range re.prog.Inst { if x.Op == syntax.InstRune || ... { a++ if x.Flag { s++ } } } return s, a }
カバレッジの計算 できた!!! func main() { re := regexp.MustCompile(`[a-z]+\.(co|ne)\.jp`) re.MatchString("hoge.co.jp") c,
a := re.Coverage() fmt.Printf("%d / %d", c, a) } 7 / 9 ※ 文字マッチ命令の 成功 / 全部 を数えた場合
まとめ 正規表現のカバレッジは( 頑張れば) 測れる Golang の正規表現実装は読みやすい テスト回のはずが正規表現回になった