★★★★★★★★★★★ 情報のロストを避けよう.同じことを何回もやらないように.言語化 # **Theme** assertionの悪い箇所を探す より具体的には「assertionのうち汎用命令で書かれている箇所を検出して,具体命令に書き換える」 汎用命令なので可読性が低い ## pros - 極めて具体的 - 目の付け所は悪くなさそう(=だれかやってるかも) - 手法が簡単.検出対象が具体的なので手法も具体的 ## cons - しょぼそう 手法が結構簡単 もう一味ほしい気はする - 可読性の向上効果が低そう(=評価しにくそう) ## Tips [spreadsheet](https://docs.google.com/spreadsheets/d/1_8-FIeSAGpsnvvi-dNED5lFYbpCQfCyuYrQsEAGKL-0/edit?usp=sharing) --- # 2024/12/22 ## 表現 ### 「書き換え」「リファクタリング」について - 論文は言及されているものを見つけられなかった - OpenRewriteは[Simplify](https://docs.openrewrite.org/recipes/java/testing/assertj/simplifychainedassertjassertions) (パターン2) と[Shorten](https://docs.openrewrite.org/recipes/java/testing/assertj/simplifyassertjassertions) (パターン1) - Simplify AssertJ chained assertions - Shorten AssertJ assertions - JetBrainsのInspectopediaの記事では[Simplify](https://www.jetbrains.com/help/inspectopedia/RsAssertEqual.html) (パターン3) - Equality assertion can be simplified - JavaではなくRustのテスト - 自分の手法がJavaだけでなく多言語にも通用することの裏付けになるかも - Datadogの記事には[Simplify](https://docs.datadoghq.com/ja/code_analysis/static_analysis_rules/java-best-practices/simplify-test-assertions-ops/) (パターン3) - Test assertions using operator comparison can be simplified - [ここ](https://jsparrow.github.io/rules/chain-assert-j-assert-that-statements.html#description)でAssertJはチェインを推奨しているとあるが、どこにそれが書かれているのか不明 Simplifyが多く使われている - 簡略化? or 単純化? ### 発見しようとしている欠陥について - Test smellに近い? - Assertion smellと呼ぶともっと近いかもしれないが、そんな表現は見つからなかった。 - Test smellの事例紹介には載ってなかった # 2024/12/16 (G3MTG) - 小さい実験も回す - リポジトリごとの割合は計算すべき # 2024/12/15 ## 実験用のリポジトリを収集 言語Java、☆500以上、☆の数降順で100個クローンする。 クローンしたリポジトリから以下のようなディレクトリ構成を見つける ``` src ├main └test ``` (つまり、main、testというディレクトリを中に持つsrcというディレクトリを探す) 見つけたディレクトリの中で書き換えて割合を検証。 2000個以上見つかってしまったのでとりあえず100個検証 AssertJは`org.assertj.core.api.Assertions.assertThat`をインポートしているかで判別 Hamcrestは`org.junit.Asser.assertThat`または`org.hamcrest.MatcherAssert.assertThat`をインポートしているかで判別 - 多少取りこぼしがある | プロジェクト | assertThat数 | パターン1 | パターン2 | パターン3 | 書換割合 | パターン4 | パターン4の割合 | ----- | ----- | ----- | ----- | ----- | ----- | ----- | ----- | | AssertJ | 11936 | 395 | 82(method: 0, index: 82) | 0 | 4.00% | 0 | 0% | | Hamcrest | 1184197 | - | 2 | 0 | 1.69E-6 | - | - | | JUnit | 351803 | 5940 | - | 1405 | 2.09% | - | - | ## 実験方針 ### 簡略化効果の実験 - 簡略化ができる割合は全体で見てどれくらいか - ライブラリごとに簡略化できる割合がどれくらい異なるか ### 簡略化の妥当性の実験 - 簡略化したテストは正常に動作するか - 変換残しはどれくらいあるか - 全部は検証できないのでランダムに抽出 --- # 2024/11/25 (G3MTG) - アサーションライブラリの変換はしないほうが良い - JUnit→HamcrestならもうAssertJにしちゃってよくない? - このライブラリにはパターン〇がありませんみたいな言い方してもよい。 --- # 2024/11/18 ## Hamcrest実験 | プロジェクト | assertThat数 | パターン1 | パターン2 | パターン3 | 書換割合 | | ----- | ----- | ----- | ----- | ----- | ----- | | dubbo-common | 541 | 106 | - | - | 19.6% | | netty | 41 | 6 | - | - | 14.6% | | kafka/streams | 3991 | 487 | - | - | 12.2% | | keycloak/services | 130 | 18 | - | - | 13.8% | ### いや待て HamcrestにはisTrue()等に相当するMatcherがない。要するにパターン1に該当するものがない パターン2をちゃんと見ないといけない - JUnitだけでテストしてるやつ(assertEqualsなど)をどう考えるか。 --- # 2024/11/18 (G3MTG) - Hamcrestやるなら書き換えまでやる - HamcrestやらないならAssertJの実験を自動化して大規模にするなど。 - 書き換えたやつがちゃんと動くのか検証 --- # 2024/11/11 (G3MTG) - やっぱり話題をhamcrestに話を広げるべきかもね - AssertJの話題に広げてもよい --- # 2024/11/11 ## 実験 | プロジェクト | assertThat数 | パターン1 | パターン2 (method, index) | パターン3 | 書換割合 | ----- | ----- | ----- | ----- | ----- | ----- | | spring-boot-actuator | 1754 | 3 | 3 (0, 3) | 0 | 0.342% | | spring-boot-autoconfigure | 6010 | 22 | 0 | 0 | 0.366% | | selenium | 3827 | 36 | 60 (50, 10) | 0 | 2.51% | | jadx | 224 | 0 | 2 (0, 2) | 0 | 0.893% | | arthas | 57 | 4 | 0 | 0 | 7.02% | | mybatis-3 | 265 | 0 | 1 (1, 0) | 1 | 0.755% | | zipkin | 718 | 1 | 0 | 0 | 0.139% | | mybatis-plus-core | 187 | 0 | 1 (1, 0) | 0 | 0.535% | | mockito-core | 1072 | 36 | 7 (5, 2) | 0 | 4.01% | | pojobuilder (star数3桁) | 303 | 0 | 0 | 0 | 0% | --- # 2024/11/05 (G3MTG) - 実例で実験しよう - ついでにどれくらいの割合で改善できてるか測ろう --- # 2024/11/04 ## 可読性の正義 - 特殊なメソッド・引数 ```java assertThat(a).X(y); ``` から ```java assertThat(a).Z(); ``` - assertThatの引数がメソッド (or フィールド) ```java assertThat(a.W()).X(y); ``` から ```java assertThat(a).Z(y); ``` - assertThatの引数が条件式 ```java assertThat(a op y).X(); ``` から ```java assertThat(a).Z(y); ``` 構文木にしたときにノードが少ないほうがいい? - アサーションのチェインもノードは少なくなるはず - assertThatの引数がメソッド (or フィールド) の場合に名は以下のようなパターンがある ```java assertThat(a.W()).X(y); ``` から ```java assertThat(a).Z(y, p(q)); ``` ↑これは減ってなさそう (むしろ増えてる?) - この改善はエラーメッセージの改善が本質ではある # 2024/10/21 (G3MTG) - やっぱり実験を進める - 指標考えるより変換前と変換後を比較して特徴を抽出 - 可読性も指標とか見るより最初に正義を決めてしまってそこから議論すべき - chainについては人によって正義が異なるから機能の提案として言及。 - 自動化に囚われすぎかも。評価もなんでもかんでも自動でできるわけではないよ。可読性もそう。 --- # 2024/10/21 ## 論文読んだ結果 先週見つけた論文の続きみたいなやつ(著者同じ)に読みやすいエラーメッセージの特徴が書いてた。 [On Designing Programming Error Messages for Novices: Readability and its Constituent Factors](https://dl.acm.org/doi/10.1145/3411764.3445696) 特徴は3つ - 専門用語を用いない - 完全な文章で記述 - 難しい語彙を用いない 自分の研究で解決できる側面とは違う気がする。 やはり変数等のアサーションに関わる値の数の多さなどで比較するのがよさそう。 - 表示してくれる値の数が変わらないものもあるけど、多くなるものは確実にある(少なくなるものは今の段階では思い当たらない)。 - 多くならないものについても可読性という観点で改善できるわけだし ## chainのメリットについて あんまり可読性が向上するとは言われていない [The Role of Method Chains and Comments in Software Readability and Comprehension—An Experiment](https://ieeexplore.ieee.org/abstract/document/7404062) ではメソッドチェーンは可読性に有意差が出なかったとのこと [Hamcrest vs AssertJ: an Empirical Assessment of Tester Productivity](https://sepl.dibris.unige.it/publications/2019-leotta-QUATIC.pdf) ではAssertJのchain機能について触れているが、そこまでめっちゃ推してるわけでもない。 --- # 2024/10/15(G3MTG) 考えすぎもよくない。実験をしよう。 OpenRewriteについてどこまで触れるかは書いてみないとわからない。 --- # 2024/10/09 ## 見つけた変換 変換前 ```java assertThat(object1.hashCode()).isEqual(object2.hashCode()) ``` 返還後 ```java assertThat(object1).hasSameHashCodeAs(object2) ``` [事例(JUnit)](https://github.com/google/guava/blob/49776fd83201172368ce560bb3282d5ca568096f/guava-tests/test/com/google/common/collect/AbstractHashFloodingTest.java#L184-L186) --- # 2024/10/08 (G3MTG) - チェインしたほうがいい論拠は外から持ってきてもいい - AssertJはチェインしてほしそう - エラーメッセージを何か定量的に測れたら最高 - 被験者の必要な実験は大変 --- # 中間発表 (2024/10/07) - アサーションリファクタリングという言葉は存在する?調査せよ。 - 思ったより専門家は言葉にうるさそうだな。 **(悪口ではない)** - そらそうか… - 目的を明確に。 - どういうアサーションコードがいいのか先に話すべき。 - Bad-Goodを明確に - 言語にこだわるな。 - あくまで手段であって他の言語でも適用可能な方針がOpenRewriteとの最大の差別化。 - assertThatの引数が式である場合とメソッドである場合は区別すべき。 - もっと抽象的なコードを書こう。 - `Z.X()`など - アサーションのリファクタリングの必要性について他に言及しているものがあれば説得力が増す。 - なければ提案になる - 評価方法は、はよ決めて。 - 実際のプロジェクトで具体的にどれだけの割合を変換できるか調べよ。 - running example - 具体例は何度も引用されることが理想。 ## 指摘を受けて 自分のやろうとしていることはアサーションのリファクタリングの一部 OpenRewriteは以下のように表現 - Shorten AssertJ assertions - Simplify AssertJ assertions アサーションの単純化?みたいな? ### 評価 - 基本的には発表で言った感じでいきたい。 - 正しいかどうかの評価って論文にどう書くの? - プログラムの正しさを示すわけではなくて、どれくらいの問題を解決できるかを示すんだと思ってる。 - 変換すべきなのに変換できない箇所を見つけるということ? - ツールを使ってもらうのではなく、専用命令と汎用命令のケースを比べて可読性やエラーメッセージの分かりやすさを体験してもらう。 - 同じバグを含むプログラムにおいて、テストが汎用命令で書かれているものと専用命令で書かれているものを用意し、バグ修正にかかる時間を計測 - 他に可読性の指標は持ってきたほうがいいか ## not-chainedをchainedにできるか 変換前 ```java assertThat(list).hasSize(4); assertThat(list).contains(10); ``` 変換後 ```java assertThat(list).hasSize(4) .contains(10); ``` 可読性はよくなりそう (特に数が多い場合) エラーメッセージについては未確認 - 連続するassertThatで引数が同じものをチェインさせる - 連続するassertThatであることをどう検出する??? --- # 2024/09/30 ## AssertJ以外のライブラリについて JUnit→AssertJについてはやる意義がありそう - JUnitのアサーションがしょぼいのしかないから。 ↑ アサーションライブラリの移行については書かない予定だったでしょうが!!! - expectedとactualを正しい場所に修正 # 2024/09/30 個人mtg アサーションライブラリの混在の解消 - やってもいいかも 1章にOpenRewriteは書かない。 あんまりライブラリの乗り換えについて書くとOpenRewriteと真っ向勝負になる。 今までの方針をそこまで変えない。自身のツールを強化せよ。OpenRewriteにない機能を盛り、差別化へ。6章とか7章にOpenRewriteのことを書き、こいつにはない機能がありますよ~ってアピール JUnitとHamCrestの差別化をしっかり。 ## Tips - 補足の括弧かメソッド呼び出しかわからないので括弧の扱いには気を付けること --- # 2024/09/27 ~ 2024/09/29 ## OpenRewriteができないこと [これ](https://hirabay.net/?p=292)を参考に ### 1, list.get()→contains ```java assertThat(list.get(0)).isEqualTo(0); ``` を ```java assertThat(list).contains(0, atIndex(0)); ``` にするみたいなのはないっぽい - でもこれはhasSizeの書き換えほどわかりやすさの変化があるか微妙 - fail時のメッセージはこっちのほうがかなりわかりやすいと思う ``+ チェイン、fail時メッセージ - 可読性 ### 2, length(field)→hasSize 配列のフィールド変数lengthを呼び出してるやつをhasSizeに書き換えない。 - 配列は最初からサイズ決まってるからあまり必要とされない? ```java assertThat(getList().length).isEqualTo(1); ``` を ```java assertThat(getList()).hasSize(1); ``` ### 3, 括弧があると変換できない ```java assertThat((getList().size())).isEqualTo(1); ``` を変換できない。 ```java assertThat(getList().size()).isEqualTo((1)); ``` は変換できた。 ```java assertThat((getList()).size()).isEqualTo(1); ``` も変換できた。 - 多分`size()`を見つけられてないだけ。厳密に変換するシステムを求めるなら変換すべきだけど、特殊条件すぎるのであまり書き換える必要がないと思われる。 - これリファクタリングツールだし機能の一部に余計な括弧の削除とかありそう。 ### 4, getClass→isInstanceOf ```java assertThat(list.getClass()).isEqualTo(ArrayList.class); ``` を ```java assertThat(list).isInstanceOf(ArrayList.class); ``` にアサーション AssertJで使ってる人は少なかったけどJUnitでは`getClass`と`assertEquals`を使って比較してる人がいた(`instanceof`と`assertTrue`もいた)。OpenRewriteにはJUnit→AssertJの機能もあるため、密かに需要があるのでは。 ### 5, pattern3が変換できない ```java assertThat(a == b).isTrue(); ``` を ```java assertThat(a).isEqualTo(b); ``` や、 ```java assertThat(list instanceof ArrayList).isTrue(); ``` を ```java assertThat(list).isInstanceOf(ArrayList); ``` など。 - 前者ほど露骨なものは少なそうだけど、後者みたいな記号じゃない条件式については多そう。 - AssertEqualsなどJUnitについてるアサーションを使ってる人の中にはAssertJどころかHamcrestも知らない(もしくはめんどくさくて使ってない)可能性 - 実際にJUnitのやつばっかり使われてるプロジェクトは結構いた。 - アサーションの種類が少ないからJUnit→AssertJに変換したときにリファクタリングできるものがかなり多いのでは。その中にOpenRewriteで漏れているものもありそう。 ### 問題 なぜか書き換えられない… - Buildは成功 - `org.openrewrite.java.testing.assertj.SimplifyChainedAssertJAssertions`のレシピは書き換えられる - `org.openrewrite.java.testing.assertj.SimplifyAssertJAssertions`のレシピは書き換えられない。 - `org.openrewrite.java.testing.assertj.JUnitAssertEqualsToAssertThat`も駄目。何がいけないのだろうか。 ## assertEqualsの不適切かもしれないアサーション `assertEquals`では1つ目の引数がexpectedで2つ目の引数がactualだが([根拠](https://junit.org/junit4/javadoc/4.8/org/junit/Assert.html))、[逆になっているもの](https://github.com/TheAlgorithms/Java/blob/f279f9d589febf61b52bb61caf89d02230e65324/src/test/java/com/thealgorithms/backtracking/PermutationTest.java#L15)も存在する。 JUnitのテストではさほど問題ないかもしれないけど、AssertJに変換したときに読みにくい。また、読みやすい形に変換できない。例えば、 ```java assertThat(1).isEqualTo(list.size()); ``` を ```java assertThat(list).hasSize(1); ``` にできない。 - OpenRewriteにexpectedとactualを修正するレシピは探した限りなさそう。 - expectedとactualに明確な違いがあるかと言われると微妙。完全に正しく修正することはできないと思う。 - ただ、リテラルや定数は確実にexpectedになるはず。 --- # 2024/09/26 ## DOING ### 汎用命令を検出するシステムを実装 - pattern1とpattern3は検出可能 - 分類については2024/07/22のメモを参照 - pattern2は途中 - AssertJのソースをみて書き換え可能なものを自動で検出する方法が難しいと感じたので1つずつ - `hasSize()`に置き換え可能なものを検出可能 ``` assertThat(list.size()).isEqualTo(1); ``` を ``` assertThat(list).hasSize(1); ``` - `atIndex(index)`で置き換え可能なものも検出可能 ``` assertThat(list.get(1)).isEqualTo(1); ``` を ``` assertThat(list).contains(1, atIndex(1)); ``` - 他にも実装すべきものもあるが、だんだんマイナーになっていくので書き換えの実装も始める。 ### OpenRewriteにAssertJの簡略化機能がある - [ドキュメント](https://docs.openrewrite.org/recipes/java/testing/assertj/simplifyassertjassertions#definition)を見る感じpattern1とpattern2の一部はすでに解決されている(変換レシピが存在する)。 - `assertThat(list.size()).isEqualTo(1)`とかは変換レシピになってない - `hasSize(0) -> isEmpty()`は変換レシピになってた。 - 変換レシピは自作できそう。 - こっちで変換レシピを自作したほうが使いやすいかもしれない。 - そうなると研究の規模が更にしょぼくなってないか… - このツールについて、アサーションの簡略化に焦点を当てた論文がないため、盲点だった。 - 実際に変換してみるまでができていないので頑張る。 --- # 2024/07/30 (G3mtg) > 変換可能なメソッドは規則性があるわけではないので地道に確認 これはAssertJのソース見れば自動的にわかるんじゃね? # 2024/07/22 (個人作業) ## 不適切なassertionの分類 ![PXL_20240722_063155710](https://hackmd.io/_uploads/ryGzTdsdA.jpg) `assertThat`の見つけ方は共通。メソッドの名前を見て`assertThat`をあればFQNを調べて一致するかを調べる。 ### 1. assertThatの引数によらず書き換え可能なもの #### 例 | 変換前 | 変換後 | | ------------------------ | ----------------------- | | `isEqualTo(true)` | `isTrue()` | | `isEqualTo(false)` | `isFalse()` | | `isEqualTo(null)` | `isNull()` | isNotEqualToも同様 | 変換前 | 変換後 | | ------------------------ | ----------------------- | | `isNotEqualTo(true)` | `isFalse()` | | `isNotEqualTo(false)` | `isTrue()` | | `isNotEqualTo(null)` | `isNotNull()` | #### 変換方法 ```java assertThat(hoge).y() ``` を考える。 `y`の名前(親かそれより上のノード)を見て変換可能な場合は書き換え。 ### 2. assertThatの引数(x)がメソッドの返り値の場合 #### 例 | 変換前 | 変換後 | | ------------------------ | ----------------------- | | `assertThat(list.size()).isEqualTo(2)` | `assertThat(list).hasSize(2)` | | `assertThat(str.length()).isGreaterThanOrEqualTo(2)` | `assertThat(str).hasSizeGreaterThanOrEqualTo(2)` | | `assertThat(object.getClass()).isEqualTo(Object.class)` | `assertThat(object).isInstanceOf(Object.class)` | #### 変換方法 ```java assertThat(something.x()).y() ``` を考える。 `something`の型、`x`の名前(引数の型も必要かも)(共に子ノード)を見て変換可能であれば`x()`を消し、`y()`を`z()`に変換 変換可能なメソッドは規則性があるわけではないので地道に確認 変換後は ```java assertThat(something).z() ``` ### 3. assertThatの引数が式の場合 | 変換前 |変換後 | | ------------------------ | ----------------------- | | `assertThat(a == b).isTrue()` | `assertThat(a).isEqualTo(b)` | #### 変換方法 `y`の名前(親かそれより上のノード)を見て該当しているメソッドのみならば書き換え(でも1つの式ですべて検証できるから2つ以上あることはないはず…) ## Hamcrestの場合 ### 1. assertEquals、assertNotEquals #### 例 | 変換前 | 変換後 | | ------------------------ | ----------------------- | | `assertEquals(x, true)` | `assertTrue(x)` | | `assertEquals(x, false)` | `assertFalse(x)` | | `assertEquals(x, null)` | `assertNull(x)` | | `assertNotEquals(x, true)` | `assertFalse(x)` | | `assertNotEquals(x, false)` | `assertTrue(x)` | | `assertNotEquals(x, null)` | `assertNotNull(x)` | #### 変換方法 ```java assertEquals(x, y) ``` を考える。(yはtrueまたはfalseまたはnull) `assertEquals`を検知、2つ目の引数をチェック。`assertEquals`を対応したメソッドに変更し、引数をxとする。 ### 2. assertThat #### 例 | 変換前 | 変換後 | | ------------------------ | ----------------------- | | `assertEquals(x.size(), 1)` | `assertThat(x, hasSize(1))` | #### 変換方法 ```java assertThat(something.x(), y()). ``` を考える。 `something`の型、`x`の名前(引数の型も必要かも)(共に子ノード)を見て変換可能であれば`x()`を消し、`y()`を`z()`に変換 変換可能なメソッドは規則性があるわけではないので地道に確認 変換後は ```java assertThat(something, z()) ``` ### 3. assertTrue、assertFalse | 変換前 | 変換後 | | ------------------------ | ----------------------- | | `assertTrue(a == b)` | `assertEquals(a, b)` | | `assertFalse(a == b)` | `assertNotEquals(a, b)` | #### 変換方法 `assertTrue`または`assertFalse`を検知。引数が条件式ならば`assertEquals`または`assertNotEquals`に変換し、第一引数を左辺に、第二引数を右辺にする。 ## 「検証対象が変化するメソッド」にもっといい言い方 探したけど見つからず… 「自分自身を返すメソッド」のほうに名前があるのでは? ↓ Navigation Methodsって呼ばれてるっぽい。 # 2024/07/22 (g3mtg) 書き換え可能な汎用assertionをパターン化しよう 1. isEqualTo(true)などassertThatの引数によらず書き換え可能なもの 2. assertThatの引数がメソッドの返り値の場合 3. assertThatの引数が式の場合 etc... 「1ができました!」みたいな報告ができると嬉しい。 そこから「assertThatの引数がメソッドの返り値の場合」を細分化する。 - 細分化した中に`list.size()`がある。現状その具体的なケースを押し出しすぎ 「検証対象が変化するメソッド」にもっといい言い方ない? # 2024/07/16 ## 一般アルゴリズム ### isEqualToのもの 1. assertThatを検出 2. 親ノードを見て、isEqualToかを確かめる。 - 引数がtrue、falseであればisTrue、isFalseに置き換え可能。 3. assertThatの引数(子ノード)を見る。 - collectionsを継承しているクラスのsizeメソッドを呼び出しているならhasSizeに置き換え可能 - 子ノードを見てcollectionsを継承しているクラスのsizeメソッドかを確認 ↑ これ複数のメソッドがchainしている場合にダメじゃねーか!!! #### 修正案1 1. assertThatのメソッドが呼び出されたのを見つけたら、ExpressionStmtのノードが来るまで親を辿る。 - これでassertThatを呼び出している式全体を取得できる。 - なんかassertThatの前にあるコメントも取ってくる。なんでやねん。 #### 修正案2 1. assertThatのメソッドが呼び出されたのを見つけたら、ExpressionStmtのノードが来るまで親を辿る。見つけたらそのひとつ前のノードを返す。 - コメントとかセミコロンとかそういうのは入ってこない。 #### 修正案3 1. assertThatのメソッドが呼び出されたのを見つけたら、ExpressionStmtのノードが来るまで親を辿る。それに至るまでのメソッドも取得し続ける。 - assertThatの引数も見る。 size()であるか確認。 - size()メソッドで終わっていてそのメソッドの呼び出し元がCollectionを継承していた場合。 2. その中にisEqualToがあれば検知。引数を調べる。 - 引数の有効な取得方法を探さなければならない。 - `getArgumants`でいける - 型検知がちょっと微妙 - trueかfalseなら書き換え可能。 ### Tips assertThatの検証用のメソッドには2パターンある - 検証対象が変わらない 例 ```java assertThat(list).hasSize(3).contains(4) ``` - 2つの内容を並列して計算 - 検証対象が変わる 例 ```java assertThat(list).element(1).isEqualTo(3); ``` - リストの要素を指定して計算 この二つをどう判別するか。 # 2024/07/16 (g3mtg) もっと上流の一般化アルゴリズムに目を向けよう。 - assertThat、isEqualToを検出する。 - assertThatの引数を検出する。 など 型を検出することはできる。JavaParserではできない可能性があるのでその場合はJDTを使おう。 - [聞いてみた](https://chatgpt.com/c/cfd7e75d-3121-409a-860b-af2fb5a11a67) ↑を参考にしてください。 - なのでsizeメソッドの調査は別にせんでええ。 --- # 2024/07/08 ## アルゴリズムについて size()メソッドがListに対するメソッドであることをどう保証すればよいのか。 - `list.size()`ならまだええけど、`object.getSomething().size()`だとわからない。 - いったんプログラムを動かして型を見る必要があるかも。 - そもそもそんなこと気にする必要なかったりする? いったん気にせずやってみた。 ↓実行結果 ① ![image](https://hackmd.io/_uploads/HyS-6fYw0.png) ② ![image](https://hackmd.io/_uploads/HySKnzKPA.png) ### List以外の`size()`メソッドは存在するのか さらっと見たところListかListを継承したオブジェクトしか使用されていない。 あとHashMapとかもあるわ。とにかくオリジナルのオブジェクトでsi なんか[激やばファイル](https://hackmd.io/Sr4hmkHpROK-0s1UBWLiyA#%E3%81%88%E3%80%81%E3%81%AA%E3%81%AB%E3%81%93%E3%82%8C)が見つかったけどこれはちょっと何の型かわからん(多分List) ### Tips chainのメソッドはASTではこんな枝分かれをするらしい。 ![image1](https://hackmd.io/_uploads/r1cbAZFDC.png) 引数はこうなる ![image2](https://hackmd.io/_uploads/BygMuftw0.png) #### [え、なにこれ?](https://github.com/MCLabyMod/LabyMod-1.9/blob/33e441754a0030d619358fc20ca545df98d55f71/ov.java#L10) # 2024/07/08 (G3MTG) [JavaParser](https://javadoc.io/static/com.github.javaparser/javaparser-core/3.26.1/overview-summary.html)も使ってみる。 - 静的解析ならこっちのほうがいいっぽい ひとまずは汎用assertionの方をやる。一貫性のないassertionについては後回し。 Todoの規模は頭に置いておいて。 # 2024/07/07 ## AST解析 JDT coreによるAST解析を試みる。 - ひとまずassertThatの引数に式があるかを判定できるようにする。 なんか子ノードの調べ方がわからん - [getStructuralProperty](https://www.ibm.com/docs/api/v1/content/SS5JSH_9.5.0/org.eclipse.jdt.doc.isv/reference/api/org/eclipse/jdt/core/dom/ASTNode.html#getStructuralProperty-org.eclipse.jdt.core.dom.StructuralPropertyDescriptor-)でいけそう ### Tips JDTだと`assertThat().isTrue()`では`isTrue()`が先に見られるっぽい。 ## Assert failの分かりやすさの確認 ### hasSizeとisEqualsToの例 #### failになるコード(hasSizeを用いる) ```java List<Integer> list = new ArrayList<Integer>(); list.add(1); list.add(2); list.add(3); assertThat(list).hasSize(4); ``` エラーメッセージ ``` java.lang.AssertionError: Expected size: 4 but was: 3 in: [1, 2, 3] ... ``` #### failになるコード(isEqualsToを用いる) ```java List<Integer> list = new ArrayList<Integer>(); list.add(1); list.add(2); list.add(3); assertThat(list.size()).isEqualTo(4); ``` エラーメッセージ ``` org.junit.ComparisonFailure: expected:<[4]> but was:<[3]> ... ``` isEqualToの場合配列を表示してくれない。かなりわかりやすくなっている。 ### 配列検証の例 #### failになるコード(hasSizeを用いる) ```java List<Integer> list = new ArrayList<Integer>(); list.add(1); list.add(2); list.add(3); assertThat(list).contains(2, atIndex(0)); ``` エラーメッセージ ``` java.lang.AssertionError: Expecting actual: 2 at index 0 but found: 1 in: [1, 2, 3] ... ``` #### failになるコード(isEqualToを用いる) ```java List<Integer> list = new ArrayList<Integer>(); list.add(1); list.add(2); list.add(3); assertThat(list.get(0)).isEqualTo(2); ``` エラーメッセージ ``` org.junit.ComparisonFailure: expected:<[2]> but was:<[1]> ... ``` これもめっちゃわかりやすくなってる。配列周りはfail時のメッセージに大きな差が出そう。 ただ配列でかいとでかい配列がそのまま出力されちゃってよくない。まぁインデックスはわかるからそれでも汎用assertよりはいい気がする。あとわざわざテストケースででっかい配列を使うことはあまりない。 # 2024/07/04 ## 指摘 > 研究の弱点は悪いアサーション(悪A)の「悪影響」が微妙に伝わりにくい点にある. 大した問題じゃなくね?と思われると良くない. 逆に言えばそこを説得できると良くなる. 対策は例えば以下. > ### 悪Aの悪影響をうまく伝える(1章2章で説得する) > - 「悪Aは可読性の低下につながるよ.例えばこういう書き方すると~(具体例,motivating exampleを使って説明する)」//★1 > - 「悪Aはassert fail時の問題特定を難しくさせるよ.例えばこういう悪Aが落ちるとそのメッセージは~」 //★2 > - ↑2つは事例ベースで,可読性とfail時メッセージの悪さを説明している.それに対して論述でごり押すこともできる.例えば「悪Aってのは<間接的>にassertしてしまっている」という表現は良さそう.当然直接のほうがよいという話になる. > ### 悪Aの悪影響,あるいは悪A修正の効果を実験で示す(後ろの実験の章で説得する) > - 悪Aがたくさんあったよ(影響が小さくても実際に数があることを示す) > - 可読性改善の実験するよ~ > - ... > > ★1はうまい事例を探してくると良い.読者の共感が得られるような明らかに読みにくそうな実例を. ★2のassert fail時のメッセージは調べてください.たぶん直接assertの方がfail理由がわかりやすいはず.間接はわかりにくいはず. ## 指摘に対して 明らかに読みにくそうな実例は一貫性のないassertionでならありそう。 汎用assert命令でかなり読みづらいと思ったのはクラスの種類検証 # 2024/07/01 ## 検出方法 [これ使おうぜ](https://github.com/kusumotolab/HowToUseJDTCore) # 2024/07/01 (G3mtg) g3mtgのときの文書はちゃんと具体例を書こう。 assertionに引っかかった場合のメッセージ大事。それ使えばassertion rouletteは大した問題ではなくなるかも。 1つずつつぶす必要はないので手を動かすこともしてみよう。 # テーマまとめ #### Assertionの妥当性 story: テストにおけるアサーションのやり方には差がある。アサーションは緩いとバグが検出できなくなるけど、厳しくしすぎてもテストに時間がかかってダメ。そこで妥当なアサーションかを見つける指標を作るよ pros: 研究的な発見という面では大きい cons: 指標どう作るの? 評価もむずい。妥当性があることをどう示すのか。 #### カスタムアサートの自動生成 story: カスタムアサートはコードの可読性を上げるために重要。しかし関数を自分で書く必要があるし書く側の負担がかなり大きい。だからみんなカスタムアサートを使わない。そこで今あるテストやテスト対象のクラスを見てカスタムアサートを自動生成しよう。 pros: 自動生成はできそう。 cons: 評価どうする?「見やすい」ことの示し方が難しい。 先行研究・ツールがあるかも #### State Coverageの改善 story: 変数の状態変化に対してどれだけアサーションを行っているかを測る"State Coverage"という概念がある。[先行研究](https://www.microsoft.com/en-us/research/wp-content/uploads/2012/01/StateCoverage.pdf)ではこのカバレッジ率は100%が理想とされていたが、本当にそうであるかは微妙。なので新しいカバレッジの指標を作る。 あるいは先行研究では変数のハッシュコードについて言及してなさそうなのでそこでカバレッジを拡張できそう。 pros: 比較対象がある cons: 指標をどう作るか難しそう ハッシュコードを拡張するにしてもこれだけだと小粒 #### pynguinの改善 story: テスト自動生成ある.evoとかpynとか.evoはとても頑張ってる.たくさんheuristics入ってる. 他方pynはそうではない.彼らのfocusが型の話になってて~~とかがうまくできない. なのでevoのheuristicsをpynに輸入するよ. 授業っぽい pynの改善自体は簡単そう.evoはソース難しそう.頑張ってメンテしててheuristicsだらけだから. 評価が簡単.pynとの比較できるから.しかも評価指標が明確.covとか.境界値うまくやってるとか. ものすごく実践的なテーマ.なんなら誰か使ってくれそう. cons: 新規性は薄い.evoの発見・工夫をpyに輸入してるだけともいえる. evo/pynチームの土俵に土足で入る感じがする. #### evosuiteのリエンジニアリング story: evosuite古い.新しいjavaでは実行もできないし新たな機能もフォローできてない.なので再設計するよ. pros: 実装系でおもろい 評価は?比較にならない.古と新は適用可能なjdkが違うので比較できない.新しいjavaでも動いたよ? cons: 実装系すぎて新規性が薄い.何か新javaへの適応以外の新たな改善点がほしい.卒論だから文句はいわれなさそうだけど. 実装難易度がかなり高そう. # 2024/06/27 theme assertionの悪い箇所を探す より具体的には「assertionのうち汎用命令で書かれている箇所を検出して,具体命令に書き換える」 汎用命令なので可読性が低い pros - 極めて具体的 - 目の付け所は悪くなさそう(=だれかやってるかも) - 手法が簡単.検出対象が具体的なので手法も具体的 - cons - しょぼそう 手法が結構簡単 もう一味ほしい気はする - 可読性の向上効果が低そう(=評価しにくそう) ## 変換方針 汎用assert命令があってその中でメソッド呼び出しなどをしていたら(条件式があったら)怪しい。 ## Todo - 同じ研究がないかを調べる(assertion refactoring,assertion smell,test smell,assertion inconsistency,inadequate assertion等,scholarと1h戦う) - Assertion rouletteやテストスメルについても調べる - 一般化可能な検出方法を決める - どう評価するべきか考える - シートの分類わけをする。 ``` 6月のまつ本から3ヶ月後の俺へメモ 以下のストーリーはあんがい適当.なんかいまいちなので直してね. // backg SW開発においてテスト重要だ 品質確保のためだ. 一般的にはテストFWを使ってやるよ.テストもプログラムだよ ~~ // readability of test テストもプログラムなので可読性が重要だ. 読みやすいほど意図がわかるしメンテも楽. テスト自体にバグがある[]とも言われるので. // we should focus on assertion, not only on execution part assertの方法は様々でここにも可読性があるはずだ. テストは実行とassertの2パーツに分解できて,実行側はいろんな研究ある. 典型的なのは網羅率.あとあれとかこれとか(研究いろいろ) 一方,assertの研究についてはroulettteとかdupとかあるけど, // this paper テストの可読性向上を目的として,不適切なassertion命令の検出,及びその自動変換に取り組むよ. ここでの不適切なassertionとは,具体的なassert命令に書き換え可能な抽象assert命令の使用を指す. 例えば,`org.junit.assert.Assertions#assertTrue(val)` はvalの値がtrueであることを確認する汎用的な命令だ. こいつは2つの値の一致にも使えるがassertTrue(val1 == val2),assertEquals(val1, val2)こっちの方が良い. ``` ## 懸念 ### 1. どう評価するか 検出結果が正しいことをどう示すのか? - 全てのassertionからあらかじめground truthを目視で作っておいて,ツールの検出結果と比較する (recall/precision) - 検出結果からランダムサンプルしてそれが正しいかを確認する (precisionのみ) draw venn diagram 変換結果が役立つことをどう示すのか? - 可読性をどう測るか? - 人に読んでもらう(どっちがテストの意図を汲み取りやすいですか?なぜそう思うか?) - 可読性の計測方法を調査する ### 2. アサーションスメルという概念まで広げるか テストスメル=テストの悪い箇所 assertionの悪い箇所を探す=テストの悪い箇所=テストスメル ``` 新たなテストスメルをN個定義するよ.以下に列挙するよ 1. 不適切な抽象assert命令の利用 2. inconsitent assert (検出むずそう) 3. assertion lib混在(検出が簡単すぎておもんなさそう.ただ変換は逆にむずそう) - existing studies has revealed DA, RA,they are also assertion smells. ``` # 2024/06/25 [star500以上を指定で検索](https://github.com/search?q=language%3AJava+stars%3A%3E500+&type=repositories&s=stars&o=desc&p=2) # 2024/06/21 ## 著名なプロジェクト [spreadsheet](https://docs.google.com/spreadsheets/d/1_8-FIeSAGpsnvvi-dNED5lFYbpCQfCyuYrQsEAGKL-0/edit?usp=sharing) ## AssertJ Assertions Generator 例えばこんなクラスがあります ```java= public class Player { private String name; private String team; private List<String> teamMates; // constructor and setters omitted public String getName() { return name; } public String getTeam() { return team; } public List<String> getTeamMates() { return teamMates; } @Override public String toString() { return "Player[name="+name+"]"; } } ``` こんなのができるようになるらしい ```java assertThat(mj).hasName("Michael Jordan") .hasTeam("Chicago Bulls") .hasTeamMates("Scottie Pippen", "Tony Kukoc"); ``` 1回試してみます(**TODO**) # 2024/06/21 個人mtg ## 指摘 言葉の選び方注意 授業っぽいプロジェクトに注意 テストスメルについて調べる - 自分の研究がテストスメルじゃないのか?と言われる - 既存の論文は実行部分も含めてのスメル Story書くときは1行に長く書かない。[これ](https://hackmd.io/Sr4hmkHpROK-0s1UBWLiyA?both#Story)を真似して ## 懸念 - 実例があるか - まともなプロジェクトから最低1~2個ほしい - 一部のプロジェクトにめちゃくちゃあったらいい。 - 不適切なassertとは何か?この概念がでかすぎる - 可能性は無限にあるので,うまい限定がいる.scope作らないといけない - 例)seertiライブラリの混在 - 例)Listに対する過剰な/ガバガバなassertion - 例)????? // リストのサイズをisEqualToで検証 - specificなassertionに入れ替え可能な汎用assert命令の利用 - その中でも特にleottaら論文が指摘する上位3件(56%)を対象にやるよ - 例)テストスメル - 例)カスタムアサートを使うべき箇所 **↑↑↑↑↑のどっかに落とし込む** ## Todo - AssertJを使った著名なプロジェクトを最低10個見つける。 - テストを見て不適切なアサーションを探す。 - Excelなどにまとめる(1行が不適切アサーションの事例 どのプロジェクトのどのファイルにどんな不適切さがあるか) - 分類分けができる - Hamcrestのやつを探してもよい (数が多いため) - 先生から頂いた論文の中のデータセットも見る (あれば) - junit5のリファクタリングの論文も見る - assrtJでどんなことができるかも調査(カスタムアサートの自動生成など) - [AssertJ Assertions Generator](https://joel-costigliola.github.io/assertj/assertj-assertions-generator.html)の調査 [Quickstart (maven)](https://joel-costigliola.github.io/assertj/assertj-assertions-generator-maven-plugin.html) ## Story ``` // backg SWテストの重要な要素としてassertionがある. テストは実行とassertだ.動かしてから期待値との合致を検証する. // prob <不適切な>アサーションがありうる. 一般的な手続き処理と同様,assertionにも表現の自由度が下手に書くと<不適切>になる. これは可読性の低下につながる. 例えばxxx (depends on <>) // this paper 自動検出して自動修正しよう ``` ## Tips ### assertion roulette 「テストには1個assertion書こうね.たくさん書かないようにしようね」 理想はこう.テストの対象オブジェクトを一つ選びだして,それに対してathatする.あとはchaining. assertjの良さをいかしつつ,rouletteを避ける方法. 可読性も高い.自然な英文になる. ```java // こんなんより assertThat(aggregationOptions).getSomething().isMatch() assertThat(aggregationOptions).getSomething().isMatch() assertThat(aggregationOptions).getSomething().isMatch() assertThat(aggregationOptions).getSomething().isMatch() // こんなん assertThat(aggregationOptions) .isSomething() .isSomething() .isSomething() .isSomething() ``` ### specificなassertionに入れ替え可能な汎用assert命令 ```java // this one assertThat(listStuff.size).isEqualsTo(3) // should be assertThat(listStuff).hasSize(3) // but how about this? assertThat(listStuff.number).isEqualsTo(3) // should be what? Is this better? really? assertThat(listStuff).extracting("number").isEaulsTo(3) //?????????? assertThat(stuff.getStuff() > 3).isTrue(); assertThat(stuff.getStuff()).isGreaterThan(3); // better assertThat(stuff).isStuffGreaterThan(3); // much better ``` # 2024/06/17 ~ 2024/06/20 assertが変更されているcommit探し - リスト・文字列を重点的に ### [1. シード値の指定 (python)](https://github.com/PMEAL/porespy/commit/061df698001158d9e5ca492cdfc133632f18cec2) シード値を固定することによってより厳しいテストをしている? - 重点的に見るべきものではなさそう。 ### [**2. リストの0番目の要素の型を検証** (python)](https://github.com/humblFINANCE/humblDATA/commit/53df50ddb87d1226fcfa6bca9d3f2004a82cc56b) humblDATAはhumblFINANCEっていう財務ツールのWebアプリに個別の社内の情報を接続させるためのソフトウェアらしい `tto_dict`メソッドは`tests/unittests/toolbox/custom_data/mandelbrot_current.pkl`からデータを取り出し、そのデータを辞書として取り出すメソッド。`test_to_dict`はそのメソッドのテスト。 commitの変更がここだけなのでバグを見逃しててテストを追加したのかはわからん - これについてのissueはない。 テストが増やされたのが[他のテストが追記されたcommit](https://github.com/humblFINANCE/humblDATA/commit/a47453cdacdab2c7eaed15c89392eaf4d3e9ade5)と同日であるため、具体的にバグが判明していたわけではなさそう。 ![image](https://hackmd.io/_uploads/H1QCQZeIC.png) 配列の1要素目を検証するかしないかというのも人によって分かれそう。 - 検証コスト低い - 配列の特定の要素が別の型になることは少なそうだから1要素目を てかこのプロジェクト配列長調べてないけど大丈夫? ### [3. assertが削除されている例](https://github.com/filipsnastins/tomodachi-testcontainers/commit/028d380da0d56318d12e1fb3aa1dd34e99aa9f70) volatileなのでテストしてないらしい。どういうこと? - 返ってくる値が毎回同じではない仕様なのかも ## [assertに関する論文](https://sepl.dibris.unige.it/publications/2019-leotta-QUATIC.pdf) (先生から渡された奴は中身見れなかったのでほぼ同じ奴) ### ざっと読んで内容まとめ 主題はHamcrestとAssertJの比較 その中でgitHubでどのようなassertionが行われてるか調査、その結果がTable 1 Table 1にあるマークの意味は? - 実験でなるべく使わないようにしてほしいやつが✘ - 規制しないとそのメソッドがめっちゃ使われるから。実験では色々なものを使ってほしい。 - 使うことが想定されているものが✔ 規制したメソッドばっかり使われてるテストは低品質だという指摘がある 様々な検証メソッドがあるにも拘らずそれらを使わずにequalToなどを用いている。可読性が下がるため、良くないという主張はいけそう。 ### [先生から頂いたまずそうなassertion](https://github.com/JetBrains/intellij-community/blob/11e07e0a4fe0e03d29b53e00e2cb7eee1ad94f26/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/ExtensionPointImplTest.java#L171-L174) どう改善すべきか ```diff= - assertThat(extensions.get(0)).isEqualTo("second"); + assertThat(extensions).contains("second", atIndex(0)); + assertThat(extensions).element(0).isEqualTo("second"); assertThat(extensions.get(1)).isIn("", "first"); assertThat(extensions.get(2)).isIn("", "first"); assertThat(extensions.get(2)).isNotEqualTo(extensions.get(1)); ``` ~~isInの一つ目の引数に空文字列が入ってるのは何だ…?~~ 検証対象にisInで指定している要素が含まれてるか検証していると勘違いしていました(実際は逆)。 細部が分かってないので言及しづらいが、0番目の要素をすべて検証するなら1番目も2番目も同じようにすべきなのでは(もしくは検証しない)。 改善してみてわかったけどやりたい内容をドキュメントから探すのがかなり手間。自動変換の価値はありそう。 #### story(仮) ソフトウェア開発においてテストの工程は重要。そのテストにおいて、変数の状態をチェックするassertionという行程がある。そのassertionのためのライブラリとしてJUnitやAssertJがある。ライブラリには検証用の様々なメソッドが存在する。例えばAssertJなら配列などに指定した要素が含まれるかを検証するためにcontainsというメソッドがある。しかし、GitHubのプロジェクトではどうやら一部の単純な検証メソッド (isEqualToなど) ばかりが使われている。中には、式の組み合わせによってほかの検証メソッドと同じ動作を実現させている。例えば以下。 ```java // 変数listのサイズが3であることを検証 assertThat(list).hasSize(3); // isTrueという単純なメソッドを用いてhasSizeメソッドと同じ挙動を実現 assertThat(list.length == 3).isTrue(); ``` 基本的に式を組み合わせないほうが可読性が高くなる。一部の単純な検証メソッドばかりが使われている理由として、検証用のメソッドは数が多く、目的に応じたメソッドを探すことは困難であることが考えられる。そこでassertionを自動で可読性が高いものに変換させる。 #### 懸念 Assertionを自動生成する研究 (見つかってないけど) に全てを包含される 実際にどれくらいのassertionが置き換え可能かわかってない。 ざっと調べた感じなさそうだけど検索が下手かも 検索したやつ - readable assertion in software testing - readable transform in software testing - assertion simplification in software testing - equivalent assertions in testing frameworks #### GPT ChatGPTなら最適なAssertionを提供できるのではと考え、検証 ##### 変数listのサイズが3であることを検証する場合 流石にhasSizeを使ってきた ##### 配列の0番目の値を検証する場合 0番目を取り出してisEqualToを使ってきた。(atIndexは使用しなかった) 自分が持ってるデータの中で一番多く使われてるものを選んでるのかな? でも読みやすさを聞いたらisEqualToと言ってきた。どうやらatIndexは複数の値を検証するときに使うべきらしい つまりこう ```diff - assertThat(list[0]).isEqualTo(1); - assertThat(list[1]).isEqualTo(2); - assertThat(list[2]).isEqualTo(3); + assertThat(list).contains(1, atIndex(0)) + .contains(2, atIndex(1)) + .contains(3, atIndex(2)); ``` 複数の値の検証時にこれやってないコードめっちゃありそう。 でも本当に後者のほうがいいのかな…? - 可読性そんなに変わらなさそう - 後者のほうがごちゃごちゃはしてないけど - assertThatがうざくなくなる - 小粒すぎる ### AssertJ使う (これは報告) 環境出来た 置き換え可能な検証メソッドについて調査しやすくなります。 ### 他の置き換え可能な奴の調査 #### [リストのサイズをisEqualToで検証](https://github.com/JetBrains/intellij-community/blob/11e07e0a4fe0e03d29b53e00e2cb7eee1ad94f26/platform/extensions/testSrc/com/intellij/openapi/extensions/impl/ExtensionPointImplTest.java#L272C31-L272C47) 同様のリストサイズ検証でhasSizeが使われているものもあったので別の人がコミットしてるっぽい #### [private変数のテスト](https://github.com/J3y0/Java_courses/blob/a3ac6954372befcff7ae338da6afd7866f2ac8ca/BE3/tests/rationals/RationalReaderTest.java#L42) `extracting`を用いてprivate変数の中身を検証している いちいち`getSometing`メソッドを用いて取り出すよりこっちのほうがよさそう。 ```java // こっちより assertThat(Object.getNum()).isEqualTo(0); // こっちのほうが可読性が高いかも? // あとgetNumをテスト以外で使わないなら実装する必要もない。 assertThat(Object).extracting("num").isEqualTo(0); ``` ~~配列は抽出できなかった。メソッドの種類多いので他に方法があるかも?~~ - 抽出できないのではなく、配列の検証用メソッドが返り値の型に用意されていないため、containsなどができない(isEqualToならいける)。 intとStringはできた。多分booleanもできる。 # 2024/06/17 (g3mtg) 特にリストや文字列について調査しよう issue assertionが増やされたコミット testのfix めちゃくちゃテストあるプロジェクト中のコミットでテストが増やされた所を検索 - issueとかfixとかついてるとよい # 2024/06/14 ## Assertionの妥当性 ### どこまでこれを続けるか 2024/06/21までには決めたい 土日で作業が進めば2024/06/17でもいいかも これ以上見ても厳しいなと感じた場合は別のに切り替え 題材一つ当たり読む時間が現状かなりかかってる ### 続けない場合どうするか 二番手考えよう Pynguinの拡張はやりやすい。 - 他の言語とかありかも?RustとかJavaやPythonなどにはない障壁があったりしないだろうか。 State Coverageの拡張にも可能性を感じる ### 題材探し #### [private変数をテストしていない](https://github.com/unclebob/fitnesse/blob/master/test/util/CommandLineTest.java) fitっていう受入テストのフレームワークがある。 FitNesseがそのフレームワークのwikiラッパー (記述支援みたいな感じかな) ##### で、CommandLineは何やってんの? なんかファイルの中にOptionクラスがあるけどテストされていない と思ったけど`hasOption`メソッドで見られてましたすんません - いったん正しいものとして扱う <details> <summary> 処理の概要 (あんまりおもろいものも見つからなかったので見なくていいかも) </summary> フィールド変数はコンストラクタでのみ更新 `parse`は`[-opt1 arg1 arg2] [-opt2 arg1] [-opt3] arg1 arg2`という入力に対して、引数の文字列が以下に従うかを検出 - オプションがある場合、opt1、opt2、opt3のどれかまたはこのうちの複数が含まれている - 引数は0か2つ - opt1がオプションの場合はopt1を指定したのちに更に2つの引数 - opt2がオプションの場合はopt2を指定したのちに更に1つの引数 - opt1、opt2、opt3とオプションに関係ない引数の並び順は任意 - 空文字列でないこと `CommandLine`のフィールド変数はオプションの種類を格納してる。 </details> `hasOption`メソッドは全検証ではないけど妥当だとは思う。 #### 方針転換 とりあえずテスト不足だと思われるものを探してそこから掘り下げ - 全部掘り下げたら時間足りない - テスト不足だと思われるものもなかなか見つからない… #### いままでのassertion方法まとめ ##### 変数の内容を全部チェック - 普通。これが妥当性が低いとはならなさそう ##### 変数の一部をチェック - 文字列や配列などで用いられる - 文字列長や配列長などが追加で検証されたりする - 検証されてないやつもある。こういうのが危うそう。なんならこれをまずいアサーションとしてもよさそう。 - 計算量が増えたりしない - 行数もそんなに増えない つまりほぼ検証し得。なのにやってないのは妥当性に欠ける。 - エラーが見逃されてる例が欲しいが、今のところ見つかってない。厳しい… - 完全一致で検証しないから穴もあるかも - この「穴がある」基準を何かしら定めたい - 配列長を検証するかしないかだけだと小粒すぎる - これが見つからなければ諦める。 - 答えが一意に定まらない可能性もある - 環境によって結果が違うなど ##### 変数の中身チェック + ハッシュ値の検証 - 要はオブジェクトの参照先が変更されたかされてないかチェック - かなり強い検証 - 行われているプロジェクトは少ない (見つかったのは1つだけ) - 全てのオブジェクトにやるのは過剰だけど一部ならそんなこともない - その境目は? - そもそもこれをやってるプロジェクトすら1つしか見つかってないないのに全部のオブジェクトでやってるやつなんかほぼない。 #### 検証対象のメソッド中に含まれるメソッドの間違いを想定したテスト 対象のメソッド内のメソッドは正しいものとして扱ってよい (その間違いはそのメソッドのテストでやるべき) - その通りなんだけど、これをやってるプロジェクトは見たことがない… # 2024/06/14(個人mtg) ☆assertionの自動生成 ☆カスタムアサートの自動生成 先行研究ないかどうか ☆assertionの妥当性調査 とりあえずまだ題材が足りない(妥当性の正義が見えてこない)ので探す。 アサーションの不十分によってバグが起きる例などがあればよい。 探して見つからないなら他の道に 前提の確認のためのテストとテスト対象の動作確認のためのテストは概念が分割される。 ### TODO カスタムアサートの自動生成の先行研究・ツールがないかどうか確認 assertionの妥当性調査については妥当性の正義や妥当性不足の可能性がまだ見つからないので題材を増やす - アサーションの不十分によってバグが起きる例 - どのようなプロジェクトなのかについても深く見ること.そのテストのコンテキストを理解しないといけない - 自分がどう考えるかを書く。これは十分なのか不十分なのか 今まででたテーマを列挙する.まとめる. ``` #### evosuiteのリエンジニアリング story: evosuite古い.新しいjavaでは実行もできないし新たな機能もフォローできてない.なので再設計するよ. pros: 実装系でおもろい 評価は?比較にならない.古と新は適用可能なjdkが違うので比較できない.新しいjavaでも動いたよ? cons: 実装系すぎて新規性が薄い.何か新javaへの適応以外の新たな改善点がほしい.卒論だkら文句はいわれなさそうだけど. 実装難易度がかなり高そう. #### pyguinの改善 story: テスト自動生成ある.evoとかpynとか.evoはとても頑張ってる.たくさんheuristics入ってる. 他方pynはそうではない.彼らのfocusが型の話になってて~~とかがうまくできない. なのでevoのheuristicsをpynに輸入するよ. pros: pynの改善自体は簡単そう.evoはソース難しそう.頑張ってメンテしててheuristicsだらけだから. 評価が簡単.pynとの比較できるから.しかも評価指標が明確.covとか.境界値うまくやってるとか. ものすごく実践的なテーマ.なんなら誰か使ってくれそう. cons: 新規性は薄い.evoの発見・工夫をpyに輸入してるだけともいえる. evo/pynチームの土俵に土足で入る感じがする. ``` いつまでassert妥当性のネタにこだわるかを考える.自分でスケジューリングする.mtgのリーダーは自分だと意識付ける. # 2024/06/10 (個人作業) ## assertionの妥当性調査 ### 1. [オブジェクトの中身全部見てる](https://github.com/bboylin/MyNotebook/blob/a6dc749faef4fc616d8c65912e902c7624ed74e0/part1/Waiter.java#L145) ```java public void assertEquals(String message, Object expected, Object actual) { if (!equalsRegardingNull(expected, actual)) { ... // ここでエラー出してる } } private boolean equalsRegardingNull(Object expected, Object actual) { // 両方nullなら一致とみなす if (expected == null) { return actual == null; } return isEquals(expected, actual); } private boolean isEquals(Object expected, Object actual) { return expected.equals(actual); // 中身全部検証してる } ``` [配列に関しても同じように全部検証してるっぽい](https://github.com/bboylin/MyNotebook/blob/a6dc749faef4fc616d8c65912e902c7624ed74e0/part1/Waiter.java#L538) ```java=538 public void assertArrayEquals(String message, float[] expecteds, float[] actuals, float delta) { try { new InexactComparisonCriteria(delta).arrayEquals(message, expecteds, actuals); } catch (ArrayComparisonFailure failure) { mAssertionError.set(failure); } } ``` カスタムアサート ### 2. [文字列だけど完全一致で照合してない](https://github.com/aro-tech/interface-it/blob/50262e342952618a6553016060b64c9a795ce0af/src/test/java/com/github/aro_tech/interface_it/api/StatisticProvidingMixinGeneratorTest.java#L83) ```java=83 private void callAndVerifyMakeDelegateMethod() { Optional<Method> verifyMethod = underTest.listStaticMethodsForClass(org.mockito.Mockito.class).stream() .filter((Method m) -> "verify".equals(m.getName()) && m.getParameters().length == 2).findFirst(); // メソッドが存在したかチェック (このへんどうでもええ) assertTrue(verifyMethod.isPresent()); // resultにはメソッドの全文が入るらしい String result = underTest.makeDelegateMethod("whatever", verifyMethod.get(), new HashSet<String>(), defaultNameSource); // containsが2つのみ assertThat(result).contains("default <T> T verify(T arg0, VerificationMode arg1) {").contains("return Mockito.verify(arg0, arg1);"); } ``` なぜ? - 長いから? - 一意ではない? - [resultに入るコード](https://github.com/mockito/mockito/blob/f1d71842367b6fe5fe60fddfb984c538450fceaa/src/main/java/org/mockito/Mockito.java#L2579C5-L2581C6)を探してみた ```java=2579 public static <T> T verify(T mock, VerificationMode mode) { return MOCKITO_CORE.verify(mock, mode); } ``` - 全然長くない - 改行は関係あるかも? - 全部チェックすべきでは? - containsだけだったら余計な文字列入っててもわからん - そんなもんないのは自明? 他にもこのプロジェクトでは完全一致で検証していないところがある - [コメント挿入コードのテスト①](https://github.com/aro-tech/interface-it/blob/50262e342952618a6553016060b64c9a795ce0af/src/test/java/com/github/aro_tech/interface_it/format/CodeFormatterTest.java#L41) - [コメント挿入コードのテスト②](https://github.com/aro-tech/interface-it/blob/50262e342952618a6553016060b64c9a795ce0af/src/test/java/com/github/aro_tech/interface_it/format/CodeFormatterTest.java#L49) - [定数の割り当てコード生成メソッドのテスト](https://github.com/aro-tech/interface-it/blob/50262e342952618a6553016060b64c9a795ce0af/src/test/java/com/github/aro_tech/interface_it/api/CoreMixinGeneratorTest.java#L66) - [よくわからんやつ](https://github.com/aro-tech/interface-it/blob/50262e342952618a6553016060b64c9a795ce0af/src/test/java/com/github/aro_tech/interface_it/ui/commandline/CommandLineMainTest.java#L378C14-L378C61) 改行あったら全部確認したくないのか? - 納得はできない _ _ _ _ ここから2024/06/13 _ _ _ _ ### 3. [文字列の一部照合](https://github.com/kiegroup/jbang-catalog/blob/addbd6476d11b26da5a9c0b0b4d47ad5b3a385e7/runTest.java#L93) ```java=93 public void testXLS2DMN() { int exitCode1 = new CommandLine(new xls2dmn()).execute(List.of("tests/Loan_approvals.xlsx").toArray(new String[]{})); Assertions.assertThat(exitCode1).isEqualTo(0); wireIO(); final String JSON = "{\"FICO Score\":765,\"DTI Ratio\":0.1,\"PITI Ratio\":0.1}"; int exitCode2 = new CommandLine(new dmn()).execute(List.of("tests/Loan_approvals.xlsx.dmn", JSON).toArray(new String[]{})); // please notice using converted file. resetIO(); String out = baos.toString().trim(); Assertions.assertThat(exitCode2).isEqualTo(0); Assertions.assertThat(out).contains("\"Loan Approval\" : \"Approved\""); } ``` コマンドを実行してるっぽい? - 文字列が実行結果っぽい そもそも結果が一意になってないのかも - picocliが全部外部からコマンド読んでたら一意にならない可能性はある - picocliが全部コマンド処理を独自で実装していたら一意になりそう 多分前者な気がする… ### 4. [参照変更の確認](https://github.com/remkop/picocli/blob/b03121b07eafaa094f634a09109f77df4b9cb4c0/src/test/java/picocli/CommandLineTest.java#L143) ```java=143 @Test public void testArrayPositionalParametersAreReplacedNotAppendedTo() { class ArrayPositionalParams { @Parameters() int[] array; } ArrayPositionalParams params = new ArrayPositionalParams(); params.array = new int[3]; int[] array = params.array; new CommandLine(params).parseArgs("3", "2", "1"); assertNotSame(array, params.array); assertArrayEquals(new int[]{3, 2, 1}, params.array); } ``` `assertNotSame`は必要なのか? `parseArgs`内で`params.array`が別オブジェクトになっているか検証したいのかな? 今まで出てきたことのないパターンで驚 でもassertしたくなるのもわかる - 他の開発者より自信がないだけ? - 比較的有名なプロジェクトなので強めのテストをしたのか? - そうっぽい [類似例](https://github.com/remkop/picocli/blob/b03121b07eafaa094f634a09109f77df4b9cb4c0/src/test/java/picocli/CommandLineTest.java#L190) ```java=190 @Test public void testListPositionalParametersAreInstantiatedIfNull() { ListPositionalParams params = new ListPositionalParams(); assertNull(params.list); new CommandLine(params).parseArgs("3", "2", "1"); assertNotNull(params.list); assertEquals(Arrays.asList(3, 2, 1), params.list); } ``` `param.list`がnullのまま渡し場合にnullで帰ってこないか検証 ### 5. [まずいテスト](https://github.com/UFSEC/testing-with-junit/blob/7facd40a133b43a6b53f9d9a86158b141e46ff7c/mmmTest.java#L22) ```java=22 @Test public void testThatGetModeReturnsTheModeAsAString(){ assertThat("4", containsString(mmm.getMode(new int[] { 1, 2, 4, 4}))); } ``` 最頻値を求めている。なぜ最頻値を求めているのに返り値がStringなのかなどいろいろ突っ込みたいところがあるけど置いておく なぜcontainsString??? 絶対に同値かどうかで調べるべき。しかも答えも一意に定まるし、Stringが返り値なら長さが異なる場合もある。絶対に検証が不十分。 でもこのプロジェクトは怪しい (初心者が作ってると思われる) 箇所がいくらかあるので題材としてあんまりかも…(あまり) <details> <summary> READMEも怪しい </summary> ↓#の後には空白入れないとダメだろ! ![image](https://hackmd.io/_uploads/S1_SD4OrC.png) </details> # 2024/06/10 (個人mtg) ### ☆State Coverageの改善 - 100%はやりすぎ - HashCodeとか[先行研究](https://www.microsoft.com/en-us/research/wp-content/uploads/2012/01/StateCoverage.pdf)では見られてなさそう。まだState Coverageの対象になりうるものがある ### ☆assertionの妥当性 材料がない 具体例が欲しい - 10件とかでは足りない - ハイパーリンク残そう - 自分のやつより他の人のやつがいいよ assertionの書き方にどういう違いがあるの?どっちのほうが望ましいの? - 内部要素が多い構造体とか書き方に個人差出そう。そういうのを探そう。 ### ☆PythonでState Coverage 4年生で研究できる期間は短い。おもろいテーマは難しい。 - 前二つが行き詰ったらコッチかなぁ。 - モチベーションとしてはおもろいテーマのほうがいいな。新規性がないのはモチベーションにもつながる (そんなに悪化するわけではないけど) # 2024/06/09 以前はPynguinの改善について考えていたが、個人mtgで新たに2つの方向性を提示していただいた。 ### 1. assertion網羅という概念について #### 概要 - テストにおいて、網羅率は動作確認部の十分性のみ計測 - 極端な例だと、網羅率100%でもassertionが1つもなければテストとして機能しない #### 調査 以前谷口さんがassertion網羅のテーマに取り組んでいた - [先行研究](https://www.microsoft.com/en-us/research/wp-content/uploads/2012/01/StateCoverage.pdf) (2012) があり、State Coverageという概念が生み出されている - ただし、主に言及されているのはState Coverageの定義とその計測アルゴリズムであり、理想の値にはあまり触れられていない - 現状読んだ限りは100%のほうが良いとされていそう - assertionの数とソフトウェアの欠陥の数に相関関係があるから100%のほうがいいという主張っぽい - ほんとか? - これに関しては谷口さんも言及していた - 先行研究があるからこのテーマをやめたんじゃないかと個人mtgでは言っていたがどうやら発展したテーマには取り組んでそう - 谷口さんはState Coverageの可視化に取り組むも途中でやめている - メモを見ている限り、評価が難しかったのでは - ちょっと興味あるけど評価の難しさを解決できるかどうか - 他に先行研究を踏まえて考えられていたネタは以下 - assertion命令の妥当性について (もう1つの方向性) - Mutation + Test failureのトレーサビリティ計測 (よくわかっていない) ### 2. assertion命令の妥当性について #### 概要 - assertion命令の妥当性を考える. - 抽象的すぎるとおそらくバグの見逃しにつながる. - 具体的すぎてもダメ #### 調査 妥当なassertionとは何か、変なassertion、まずそうなassertionが何かを考えるために実際のテストを見て調査 ##### GitHubから 適当なプロジェクトを探してテストを見てみる - メソッド実行の最終結果をアサーションに入れてるものが多く、メソッド内の逐一の変数更新はテストされていない - メソッド内の変数更新はテストの必要がないという考え? - テストの必要がある処理をメソッド内に複数繋げるべきではないということかも ##### 自身でテストを書いてみる プログラミングDのライフゲームのプロジェクトを使用 - 更新が生じるメソッドにおいて、すべての更新された変数が一致することを確認してみる (要は最大限テストしてみる) - private変数の検証が必要になったが、GitHubで探した限りそんなことをしているテストコードは見当たらなかった - 少なくともprivate変数はあるけどその変数の検証を行っていないプロジェクトはあった - private変数を取り出メソッドはすべてのprivate変数に対して用意しておくべき? - オブジェクトの内容を検証したい場合、NotNullを事前にやっておく必要ある? - やってないプロジェクトのほうが多そうだけど[やってるもの](https://github.com/gwtbootstrap/gwt-bootstrap/blob/master/src/test/java/com/github/gwtbootstrap/client/ui/AbbreviationTest.java)もある - 自分でやったまずそうなテスト (わかりやすさ確保のため一部改変) ```java= public BoardModel(int c, int r) { this.cols = c; this.rows = r; this.cells = new boolean[rows][cols]; this.history = new ArrayDeque<>(); } ``` に対して ```java= void testConstructer(){ BoardModel boardModel = new BoardModel(4, 4) ... for (int i = 0; i < 4; i++) { assertArrayEquals(boardModel.cells[i], new boolean[4]); } ... } ``` - 自明な気がする - こっちのほうが自明ではなさそうだけど結局検証内容は同じ ```java= void testConstructer(){ BoardModel boardModel = new BoardModel(4, 4) ... for (int i = 0; i < 4; i++) { assertArrayEquals(boardModel.cells[i], new boolean[] {false, false, false, false}); } ... } ``` - 1メソッド内で変数の更新が多くてテストが不十分かも? - これはassertion網羅率の関連の問題 - メソッドの組み方によってassertion網羅率が変わりそう (メソッド実行の結果で検証を行う場合) - 操作を細かくメソッドに分ければ分けるほどassertion網羅率を高くできる - メソッドの組み方って差が出るものなのか? # 2024/06/07 ### ★assertion網羅 ![image](https://hackmd.io/_uploads/Sk2fXxgrC.png) - いくつの作用(変数の変更)でアサーションをしたか +a加えるならassertion命令の適切さも考えると良い. 研究全体の目的をassert網羅ではなく,「assertの良さ」に格上げして, その良さを表現する考えとしてassert網羅とassert命令の妥当性??みたいな2つのサブ要素に分解する. 谷口がいったん調べてそう https://github.com/kusumotolab/Group3/blob/55894d88ade5749004326a17ffd3a3725289d80c/obog/m-tanigt/2021_m-tanigt.md#done-14 なぜやめたのか?論文があるからっぽい https://www.microsoft.com/en-us/research/wp-content/uploads/2012/01/StateCoverage.pdf ![image](https://hackmd.io/_uploads/BkkWnexBA.png) というわけでassertion網羅は苦しい.もちろん改善方向の研究はあり得る. todo: 論文(State Coverage)を読んで改善点がないか探す abstract読んだところ、重要視なのは以下の二つ - アサーション網羅率の定義 - アサーション網羅率の計測アルゴリズム つまり網羅率が100%のほうがいいのかとかそういう言及はメインじゃなさそう。谷口さんも指摘 ![image](https://hackmd.io/_uploads/HyhWoQlBR.png) 谷口さんはこの後可視化を選択 可視化について、どういう方針でやろうとしていたのか?1章が軽く書かれているので読む https://hackmd.io/cm_1jHR_T4SUNyRxDkzN4g - 評価実験が難しそう ![image](https://hackmd.io/_uploads/r1mRWEgS0.png) 先行研究を見つけた後もState Coverageについて書いてるので先行研究があるからやめたわけではなさそう 12/1のセミナー発表でうまく伝わらなかったからやめた? - 12/7と12/14の間でテーマ変えてる このときM1なんで時間がないからやめたわけではないはず ### ★assertion命令の妥当性 assertion網羅ではなく命令の妥当性を考える. 抽象的すぎるとおそらくバグの見逃しにつながる. 過度に具体的すぎてもNGだし.一般化してないから? todo: assertの具体例を見る. for what 妥当なassertionが何かを考える.あるいは過剰なassertionがないか, 変なassertion,まずそうなassertionが何かを考える #### assert使用例 実行結果の内容をassertしてるのがほとんどな気がする - 逐一変数が変わるたびにテストする必要はないと考えられてる??? - 完成版にそれがある必要ないのかも - 実行結果にも色々ある - どこが実行結果? ##### [その1](https://github.com/TBERT31/P5-yoga-app/tree/9ec8dd66e3254f6954d34ce5ae54d5240a9a0428/back/src/test) クラスの変換がうまくいってるかテストしてる nullじゃないかテスト→中身が同じかテスト ##### [その2](https://github.com/TheAlgorithms/Java/tree/master/src/test/java/com/thealgorithms) アルゴリズムのテスト 関数ごとに実行結果でテスト - 関数内の1操作ごとのテストは必要ないという考えか - どこまで関数にするかは個人の裁量にもよる気が - そんなことないかも? (少なくともよしとされている法則はあるだろうし) ##### [その3](https://github.com/spring-projects/spring-boot/tree/main/buildSrc/src/test) private変数があるけどその中身を直接持ってきてテストはしてない。 ##### TODO テスト作成の文化に慣れるために何らかのコードのテストを自分で作ってみる - 実際に体験してどれが冗長なテストか判断 ### ★pynguinの改善 pynguinは型推論周りを頑張っていて,evosuiteほどのheuristicsが入ってなさそう. つまりテスト生成の性能が低そう. (evosuiteチーム=pynguinチームだが,著者を調べたところ上記のような研究はなかった) なので改善するよ.evoとかあるいは他のtestgenツールの良い点をpynguinに突っ込む pyの需要が高いという話をうまく引き出して,py自動生成の改善の正当性を主張する必要あり todo: pynguinのできないところを探す.弱点を叩いて試してみる. ### ★evosuiteのリエンジニアリング evoは古すぎる.新しいjavaに対応できてないし,もちろん新記法にも対応できてない. あとあえていうならこういう(まだ未発見の)弱点もある.(これを見つけられたらいいな) なので作り直すよ~ (カバレッジも上がればうれしい) **~~テスト自動生成を用いて競技プログラミングのテスト不足を検出する。~~ (DEAD)** # 2024/06/07 (個人MTG) ## GPTのテスト自動生成 Pynguin(ピングイン) - テスト2はもっと実用的なコードでやったほうが良い - 変数名とかもちゃんとつけるべき - 型推測は実行したらわかるのに必要ある? - **EvoSuiteにはあるけどPynguinにない機能を探したい**←重要 ## アサーションすべき変数はどれか アサーション網羅率 ☆ # 2024/06/06 (個人作業) ## pynguinで0でraiseしたとき - ちゃんと0をテストする - 流石にか~ - EvoSuiteもPynguinも標準出力のテストはできない - なんとかならん? - 出力が違うだけの追加機能をテストできない (出力文字列があってるかわからん) - そこまで需要ないのか…? ## Chat-GPTによるテスト生成 JSのテスト自動生成にAIが使われているという情報を見て、そういえばChat-GPTだとどうなるんだろうと発想 - Pynguinの論文にパッと見AIへの言及はなし なんかPynguinより精度よくね? <details> <summary> テスト1 </summary> ### 入力 以下のPythonコードについて、できるだけカバレッジが高くなるようなテスト群を生成してください ```python= def type_test(parameter): p = 1 if parameter > p: print("Go") else: print("Stop") ``` ### 出力 以下に与えられた`type_test`関数のためのテスト群を示します。テストカバレッジを最大化するために、異なるタイプおよび境界値を含む複数のテストケースを用意します。 #### `type_test`関数 ```python= def type_test(parameter): p = 1 if parameter > p: print("Go") else: print("Stop") ``` #### テストケースの例 - `parameter`が`p`より大きい場合 - `parameter`が`p`と等しい場合 - `parameter`が`p`より小さい場合 - `parameter`が負の数の場合 - `parameter`がゼロの場合 - `parameter`が`None`の場合 - `parameter`が文字列の場合 - `parameter`がリスト、辞書、タプルなどの場合 - `parameter`が浮動小数点数の場合 これらのテストケースを考慮したPythonのテストコードは以下の通りです: ```python= import unittest from io import StringIO import sys # type_test関数 def type_test(parameter): p = 1 if parameter > p: print("Go") else: print("Stop") # テストクラス class TestTypeTest(unittest.TestCase): def setUp(self): # 標準出力のキャプチャ用 self.held, sys.stdout = sys.stdout, StringIO() def tearDown(self): # 標準出力の復元 sys.stdout = self.held def test_greater_than_one(self): type_test(2) self.assertEqual(sys.stdout.getvalue().strip(), "Go") def test_equal_to_one(self): type_test(1) self.assertEqual(sys.stdout.getvalue().strip(), "Stop") def test_less_than_one(self): type_test(0) self.assertEqual(sys.stdout.getvalue().strip(), "Stop") def test_negative_value(self): type_test(-1) self.assertEqual(sys.stdout.getvalue().strip(), "Stop") def test_zero(self): type_test(0) self.assertEqual(sys.stdout.getvalue().strip(), "Stop") def test_none(self): with self.assertRaises(TypeError): type_test(None) def test_string(self): with self.assertRaises(TypeError): type_test("string") def test_list(self): with self.assertRaises(TypeError): type_test([1, 2, 3]) def test_dict(self): with self.assertRaises(TypeError): type_test({"key": "value"}) def test_tuple(self): with self.assertRaises(TypeError): type_test((1, 2)) def test_float_greater_than_one(self): type_test(1.5) self.assertEqual(sys.stdout.getvalue().strip(), "Go") def test_float_equal_to_one(self): type_test(1.0) self.assertEqual(sys.stdout.getvalue().strip(), "Stop") def test_float_less_than_one(self): type_test(0.5) self.assertEqual(sys.stdout.getvalue().strip(), "Stop") # テストの実行 if __name__ == "__main__": unittest.main() ``` #### テストケースの説明 1. `test_greater_than_one`:`parameter`が`p`より大きい場合 2. `test_equal_to_one`:`parameter`が`p`と等しい場合 3. `test_less_than_one`:`parameter`が`p`より小さい場合 4. `test_negative_value`:`parameter`が負の数の場合 5. `test_zero`:`parameter`がゼロの場合 6. `test_none`:`parameter`が`None`の場合(TypeErrorを期待) 7. `test_string`:`parameter`が文字列の場合(TypeErrorを期待) 8. `test_list`:`parameter`がリストの場合(TypeErrorを期待) 9. `test_list`:`parameter`がリストの場合(TypeErrorを期待) 10. `test_dict`:`parameter`が辞書の場合(TypeErrorを期待) 11. `test_tuple`:`parameter`がタプルの場合(TypeErrorを期待) 12. `test_float_greater_than_one`:`parameter`が浮動小数点数で `p`より大きい場合 13. `test_float_equal_to_one`: parameter が浮動小数点数で`p`と等しい場合 14. `test_float_less_than_one`: parameter が浮動小数点数で`p`より小さい場合 このテスト群は、関数の正常系および異常系の動作を網羅しており、可能な限りのカバレッジを提供します。 </details> <details> <summary> テスト1.1 </summary> ### 入力 以下のPythonコードについて、できるだけカバレッジが高くなるようなテスト群を生成してください ```python= def test(parameter): if parameter > 0: print("3") print("2") print("1") else: print("2") print("1") ``` ### 出力 以下に与えられた`test`関数のためのテスト群を示します。関数は、引数`parameter`が正の数かどうかによって異なるメッセージを出力します。テストカバレッジを最大化するために、異なるケースを含む複数のテストケースを用意します。 #### `test`関数 ```python= def test(parameter): if parameter > 0: print("3") print("2") print("1") else: print("2") print("1") ``` #### テストケースの例 - `parameter`が正の数の場合 - `parameter`が0の場合 - `parameter`が負の数の場合 - `parameter`がNoneの場合 - `parameter`が文字列の場合 - `parameter`がリスト、辞書、タプルなどの場合 - `parameter`が浮動小数点数の場合 これらのテストケースを考慮したPythonのテストコードは以下の通りです: ```python= import unittest from io import StringIO import sys # test関数 def test(parameter): if parameter > 0: print("3") print("2") print("1") else: print("2") print("1") # テストクラス class TestTestFunction(unittest.TestCase): def setUp(self): # 標準出力のキャプチャ用 self.held, sys.stdout = sys.stdout, StringIO() def tearDown(self): # 標準出力の復元 sys.stdout = self.held def test_positive_integer(self): test(5) self.assertEqual(sys.stdout.getvalue().strip(), "3\n2\n1") def test_zero(self): test(0) self.assertEqual(sys.stdout.getvalue().strip(), "2\n1") def test_negative_integer(self): test(-3) self.assertEqual(sys.stdout.getvalue().strip(), "2\n1") def test_none(self): with self.assertRaises(TypeError): test(None) def test_string(self): with self.assertRaises(TypeError): test("string") def test_list(self): with self.assertRaises(TypeError): test([1, 2, 3]) def test_dict(self): with self.assertRaises(TypeError): test({"key": "value"}) def test_tuple(self): with self.assertRaises(TypeError): test((1, 2)) def test_positive_float(self): test(1.5) self.assertEqual(sys.stdout.getvalue().strip(), "3\n2\n1") def test_negative_float(self): test(-1.5) self.assertEqual(sys.stdout.getvalue().strip(), "2\n1") # テストの実行 if __name__ == "__main__": unittest.main() ``` #### テストケースの説明 1. `test_positive_integer`:`parameter`が正の整数の場合 2. `test_zero`:`parameter`が0の場合 3. `test_negative_integer`:`parameter`が負の整数の場合 4. `test_none`:`parameter`が`None`の場合(TypeErrorを期待) 5. `test_string`:`parameter`が文字列の場合(TypeErrorを期待) 6. `test_list`:`parameter`がリストの場合(TypeErrorを期待) 7. `test_dict`:`parameter`が辞書の場合(TypeErrorを期待) 8. `test_tuple`:`parameter`がタプルの場合(TypeErrorを期待) 9. `test_positive_float`:`parameter`が正の浮動小数点数の場合 10. `test_negative_float`:`parameter`が負の浮動小数点数の場合 このテスト群は、関数の正常系および異常系の動作を網羅しており、可能な限りのカバレッジを提供します。 </details> <details> <summary> テスト2 </summary> ### 入力 以下のコードに関しても同様にテスト群を生成してください。 ```python= def test(parameter): parameter.sort() for i in range(len(parameter) - 1): if (parameter[i + 1] - parameter[i] > 5): print("No") return print("Yes") ``` ### 出力 以下に与えられた`test`関数のためのテスト群を示します。関数は、リストをソートし、連続する要素の差が5を超えるかどうかをチェックします。テストカバレッジを最大化するために、異なるケースを含む複数のテストケースを用意します。 #### `test`関数 ```python= def test(parameter): parameter.sort() for i in range(len(parameter) - 1): if (parameter[i + 1] - parameter[i] > 5): print("No") return print("Yes") ``` #### テストケースの例 - 連続する要素の差が5を超えないリスト - 連続する要素の差が5を超えるリスト - 空のリスト - 要素が1つだけのリスト - 全て同じ要素のリスト - 負の数を含むリスト - 要素がバラバラの順序のリスト - 浮動小数点数を含むリスト - 既にソートされているリスト これらのテストケースを考慮したPythonのテストコードは以下の通りです: ```python= import unittest from io import StringIO import sys # test関数 def test(parameter): parameter.sort() for i in range(len(parameter) - 1): if (parameter[i + 1] - parameter[i] > 5): print("No") return print("Yes") # テストクラス class TestTestFunction(unittest.TestCase): def setUp(self): # 標準出力のキャプチャ用 self.held, sys.stdout = sys.stdout, StringIO() def tearDown(self): # 標準出力の復元 sys.stdout = self.held def test_no_difference_greater_than_five(self): test([1, 2, 3, 4, 5]) self.assertEqual(sys.stdout.getvalue().strip(), "Yes") def test_difference_greater_than_five(self): test([1, 7, 2, 3]) self.assertEqual(sys.stdout.getvalue().strip(), "No") def test_empty_list(self): test([]) self.assertEqual(sys.stdout.getvalue().strip(), "Yes") def test_single_element_list(self): test([1]) self.assertEqual(sys.stdout.getvalue().strip(), "Yes") def test_all_elements_same(self): test([5, 5, 5, 5]) self.assertEqual(sys.stdout.getvalue().strip(), "Yes") def test_with_negative_numbers(self): test([-10, -5, 0, 3]) self.assertEqual(sys.stdout.getvalue().strip(), "No") def test_unsorted_list(self): test([10, 1, 4, 2]) self.assertEqual(sys.stdout.getvalue().strip(), "Yes") def test_with_floats(self): test([1.0, 2.5, 3.3, 8.0]) self.assertEqual(sys.stdout.getvalue().strip(), "No") def test_already_sorted(self): test([1, 2, 3, 4, 5]) self.assertEqual(sys.stdout.getvalue().strip(), "Yes") # テストの実行 if __name__ == "__main__": unittest.main() ``` #### テストケースの説明 1. `test_no_difference_greater_than_five`: 全ての連続する要素の差が5を超えないリスト 2. `test_difference_greater_than_five`: 連続する要素の差が5を超えるリスト 3. `test_empty_list`: 空のリスト 4. `test_single_element_list`: 要素が1つだけのリスト 5. `test_all_elements_same`: 全て同じ要素のリスト 6. `test_with_negative_numbers`: 負の数を含むリスト 7. `test_unsorted_list`: 要素がバラバラの順序のリスト 8. `test_with_floats`: 浮動小数点数を含むリスト 9. `test_already_sorted`: 既にソートされているリスト このテスト群は、関数の正常系および異常系の動作を網羅しており、可能な限りのカバレッジを提供します。 </details> ### 所感 - 標準出力もテストしてくれるやん。 - [x] 複数行はどうなの? - テスト1.1 - いけそう - テスト2に関しては数値のリスト型であることを理解してテスト生成してる - いくつかテスト結果が間違ってる (テスト2) - 実際に実行しないから - 有害なコードの実行を防ぐため - 少なくとも型推測についてはPynguinより精度よさそう - [x] コードが複雑な場合どうなん? - 次の項目でやります ## AIによる型特定 ### [ABC356Eの回答](https://atcoder.jp/contests/abc356/submissions/54264069)より ```python= def solve(n, a): # 数列aを入れ替えても答えは変わらないので、事前に昇順にソートしておく a = sorted(a) a_max = max(a) m_max = 10**6 + 1 # a[i] = xとなるiの個数の累積話 c = [0] * (m_max+1) for x in a: c[x] += 1 for x in range(m_max): c[x+1] += c[x] ans = 0 for x in range(1, m_max+1): d = c[x] - c[x-1] for mx in range(x, m_max+1, x): m = mx // x lb = mx # (a[j] // d) == mとなる下限 ub = min(m_max, mx+x-1) # (a[j] // d) == mとなる上限 ans += m * (c[ub] - c[lb-1]) * d ans -= d * (d+1) // 2 return ans ``` - 型ヒントをGPTにつけてもらった結果 ```python= from typing import List def solve(n: int, a: List[int]) -> int: # 数列aを入れ替えても答えは変わらないので、事前に昇順にソートしておく a = sorted(a) a_max = max(a) m_max = 10**6 + 1 # a[i] = xとなるiの個数の累積話 c = [0] * (m_max + 1) for x in a: c[x] += 1 for x in range(m_max): c[x + 1] += c[x] ans = 0 for x in range(1, m_max + 1): d = c[x] - c[x - 1] for mx in range(x, m_max + 1, x): m = mx // x lb = mx # (a[j] // d) == mとなる下限 ub = min(m_max, mx + x - 1) # (a[j] // d) == mとなる上限 ans += m * (c[ub] - c[lb - 1]) * d ans -= d * (d + 1) // 2 return ans ``` - できてる ### [ABC356Fの回答](https://atcoder.jp/contests/abc356/submissions/54241084)から少し改変 ```python= from atcoder.lazysegtree import* from atcoder.segtree import* q,k=map(int,input().split()) tx=[[*map(int,input().split())]for i in range(q)] d={v:i for i,v in enumerate(sorted({j+2*i[1]for i in tx for j in[0, 2*k+1]}|{-1<<63,1<<63}))} s1=LazySegTree(min,1<<32,int.__add__,int.__add__,0,[0]*len(d)) s2=SegTree(int.__add__,0,len(d)) def solve(tx): for t,x in tx: x*=2 if t==1: s2.set(d[x],y:=s2.get(d[x])^1) s1.apply(d[x],d[x+2*k+1],-(-1)**y) else:print(s2.prod(s1.min_left(d[x],(0).__lt__),s1.max_right(d[x],(0).__lt__))) ``` - 型ヒントをGPTにつけてもらった結果 ```python= from typing import List, Tuple from atcoder.lazysegtree import LazySegTree from atcoder.segtree import SegTree # 入力の読み取り q, k = map(int, input().split()) tx: List[Tuple[int, int]] = [tuple(map(int, input().split())) for _ in range(q)] # ディクショナリdの作成 d = {v: i for i, v in enumerate(sorted({j + 2 * i[1] for i in tx for j in [0, 2 * k + 1]} | {-1 << 63, 1 << 63}))} # セグメントツリーの初期化 s1 = LazySegTree(min, 1 << 32, int.__add__, int.__add__, 0, [0] * len(d)) s2 = SegTree(int.__add__, 0, len(d)) def solve(tx: List[Tuple[int, int]]) -> None: for t, x in tx: x *= 2 if t == 1: s2.set(d[x], y := s2.get(d[x]) ^ 1) s1.apply(d[x], d[x + 2 * k + 1], -(-1) ** y) else: print(s2.prod(s1.min_left(d[x], (0).__lt__), s1.max_right(d[x], (0).__lt__))) # solve関数の呼び出し solve(tx) ``` - できてる - どういう例が型推測難しいかいまいちわからない。 - type inferenceの論文でやってる例とかでも試してみたい - GPTでやってる研究もありそう - [単語の意味から推測](https://speakerdeck.com/kokuyouwind/rbs-meets-llms-type-inference-using-llm?slide=1) - これはちょっと違うか? # 2024/06/02 (個人作業) - [JSのテスト自動生成](https://github.com/jellydn/auto-testgen-nodejs) - AI使ってるっぽい? - そういやChat-GPTはどんなテストを出してくれるんだろ - テストだけ出してカバレッジは計算できるの? - [Kotlinのテスト自動生成](https://www.diffblue.com/resources/automatically-generate-unit-tests-for-kotlin-projects/) - まぁそらあるか… - 作者はEvoSuiteとはちがうっぽい? - やったとて新規性一番薄そう… # 2024/06/02 (個人MTG) evosuiteは探索によって自動生成を行っている。 - evosuiteの後発研究 - スクラッチの自動生成 - **PynguinとEvoSuiteの開発者一緒** - 動的型付け言語で自動生成することが目標だからevosuiteの機能全部移してるわけではない - Pythonの型推測 (type inference) - Pynguinもやってそう - Pynguinの改善 - 学術的な発見は薄い - やるなら3つぐらい改善点 - 大きな奴一つか小さいやつ3~4個 - 境界値 - できればカバレッジで戦いたい - Pynguinが苦手なプロジェクト - 型周りは攻めにくい (論文がそこを徹底的にやってるので) - JavaScriptで同じことやる ← この辺興味あり - TypeScriptを活用 - rustとかでも ## TODO - pynguinで0でraiseしたときに0をテストするか。 - 論文読みながらPynguin使ってみる - EvoSuiteのソース見る - 特異なルール - 他言語でも自動生成できないか考える # 2024/06/02 pynguinの改善を考えるため、pynguinとevosuiteの比較(テスト生成アルゴリズムが違うのでは) (evosuite、テスト生成後に謎のエラーが出ますが一応生成できました(テスト生成後そのテストを実行しようとしてる時のエラーかな)) - コード - pynguin ```python= def f(x: int) -> str: if x > 100: return "over100" else: return "100ika" ``` - evosuite ```java= public class BoarderTest { /** * @param n * @return */ public String func(int n) { if (n > 100) { return "over100"; } else { return "100ika"; } } } ``` - 生成結果 - pynguin ``` x = 1996 x = -2318 ``` - evosuite ``` n = 100 n = 4995 n = 0 ``` - `n = 100`のみ複数回生成しても同じものが出てきた - 条件式の境界を調べている。 - `n = 101`は調べないんかい - カバレッジ - pynguin branches coverage: 100% - evosuite Resulting test suite's coverage: 92% - どこまで境界を調べてくれるのか検証 - コード① ```java= public class BoarderTest1 { public String func(int n, int m) { if (n + m > 100) { return "over100"; } else { return "100ika"; } } } ``` - 生成結果① 1回目 ``` n = 0, m = 100 n = 1, m = 100 n = -705, m = -705 ``` 2回目 ``` n = 0, m = 100 n = 2762, m = -2505 n = 1, m = 1 ``` - 1回目で両方の境界条件を見てくれたのはマグレか - カバレッジ 1回目 ``` * Going to analyze the coverage criteria * Coverage analysis for criterion LINE * Coverage of criterion LINE: 100% * Total number of goals: 4 * Number of covered goals: 4 * Coverage analysis for criterion BRANCH * Coverage of criterion BRANCH: 100% * Total number of goals: 3 * Number of covered goals: 3 * Coverage analysis for criterion EXCEPTION * Coverage of criterion EXCEPTION: 100% (no goals) * Coverage analysis for criterion WEAKMUTATION * Coverage of criterion WEAKMUTATION: 100% * Total number of goals: 20 * Number of covered goals: 20 * Coverage analysis for criterion OUTPUT * Coverage of criterion OUTPUT: 33% * Total number of goals: 3 * Number of covered goals: 1 * Coverage analysis for criterion METHOD * Coverage of criterion METHOD: 100% * Total number of goals: 2 * Number of covered goals: 2 * Coverage analysis for criterion METHODNOEXCEPTION * Coverage of criterion METHODNOEXCEPTION: 100% * Total number of goals: 2 * Number of covered goals: 2 * Coverage analysis for criterion CBRANCH * Coverage of criterion CBRANCH: 100% * Total number of goals: 3 * Number of covered goals: 3 * Generated 3 tests with total length 6 * Resulting test suite's coverage: 92% (average coverage for all fitness functions) * Generating assertions * Resulting test suite's mutation score: 100% ``` 2回目 ``` * Going to analyze the coverage criteria * Coverage analysis for criterion LINE * Coverage of criterion LINE: 100% * Total number of goals: 4 * Number of covered goals: 4 * Coverage analysis for criterion BRANCH * Coverage of criterion BRANCH: 100% * Total number of goals: 3 * Number of covered goals: 3 * Coverage analysis for criterion EXCEPTION * Coverage of criterion EXCEPTION: 100% (no goals) * Coverage analysis for criterion WEAKMUTATION * Coverage of criterion WEAKMUTATION: 100% * Total number of goals: 20 * Number of covered goals: 20 * Coverage analysis for criterion OUTPUT * Coverage of criterion OUTPUT: 33% * Total number of goals: 3 * Number of covered goals: 1 * Coverage analysis for criterion METHOD * Coverage of criterion METHOD: 100% * Total number of goals: 2 * Number of covered goals: 2 * Coverage analysis for criterion METHODNOEXCEPTION * Coverage of criterion METHODNOEXCEPTION: 100% * Total number of goals: 2 * Number of covered goals: 2 * Coverage analysis for criterion CBRANCH * Coverage of criterion CBRANCH: 100% * Total number of goals: 3 * Number of covered goals: 3 * Generated 3 tests with total length 6 * Resulting test suite's coverage: 92% (average coverage for all fitness functions) * Generating assertions * Resulting test suite's mutation score: 70% ``` - どうやらevosuiteはブランチ以外のカバレッジも観てるっぽい(Pynguinはブランチだけ) - コード② ```java public class BoarderTest2 { public String func(int n, int m) { if (n + m * n > 100) { return "over100"; } else { return "100ika"; } } } ``` - 生成結果② ``` n = 100, m = 0 n = -1685, m = -1685 n = -1685, m = 0 ``` - evosuiteは以下の要素を多くカバーしようとする - LINE - BRANCH - EXCEPTION - WEAKMUTATION - OUTPUT - METHOD - METHODNOEXCEPTION - CBRANCH - それぞれどういう意味なのかドキュメントにはなかった - pynguinのドキュメントはこれらに触れられている部分が見つからなかったため、カバーを試みていないと考えられる。 ## 競技プログラミングの問題に適用 - [ABC188Fのafter_contestのみWA](https://atcoder.jp/contests/abc188/submissions/19362969) - 境界条件の確認が不十分なケースはある程度行けそうだと考えた。 - アルゴリズムの違いも検出できるか試してみる。 - after_contest含めてACのコードしか通らないケースを出してきた…が <details> <summary> なにこれ? </summary> ```java @Test(timeout = 4000) public void test2() throws Throwable { Solver solver0 = new Solver(1L, 1431L); solver0.solve(); solver0.solve(); solver0.solve(); solver0.solve(); solver0.solve(); solver0.solve(); solver0.solve(); solver0.solve(); solver0.solve(); solver0.solve(); // Undeclared exception! solver0.solve(); } ``` - 何の値とも照合していない - after_contestのみWAのコードでは15、正しいコードでは14と出る。 </details> ## TODO - evosuiteの論文を探す(どのようなカバレッジを目指しているのか) - evosuiteの謎テストの真相を調査(既にやって見つかってないので頓挫する可能性有) - evosuiteとPynguinの更なる比較 # 2024/05/28 テスト自動生成でテスト不備見つける案はほぼ死 - 競技プログラミングで他にできることないか - 難易度推定 - できんのか? - 難しそう - 問題文が類似しているからといって難易度が近いとは限らない。 - 機能差検出の実践 - 実践できる授業があるか - 競技プログラミングや機能差検出に関わらず他に何か - 授業で学生に喜んでもらえそうな何か - ソースコードの短さ - 実行時間の長さ - Pynguinの改善 - Pynguinをいっぱい使って問題点を出す - Evosuiteみたいなヒューリスティクにテストを出す手法がないならばそれを持って来てよし ## TODO - Pynguinもっと触ってみる - 他に気になるテーマを探す ## tips - constraintで自動で引数の制約見つけれる。 # 2024/05/27 (個人作業) ## 検出テスト - [ABC188のF問題](https://atcoder.jp/contests/abc188/tasks/abc188_f)をテスト - [追加テストケースのみWAのコード](https://atcoder.jp/contests/abc188/submissions/50441751) - [追加テストケース含めてACのコード](https://atcoder.jp/contests/abc188/submissions/53800330) - 検出できず… - アルゴリズム自体の間違いっぽいけどこれも失敗 - テストの数が多くなれば(以下略) - 複雑なテストだと全部検証しきれない問題もある - このカバレッジ率は信用できるのか?みたいなのもあった - 出力されたテスト ```python # Automatically generated by Pynguin. import ABC188F as module_0 def test_case_0(): int_0 = 1619 int_1 = module_0.solve(int_0, int_0) assert int_1 == 0 def test_case_1(): int_0 = -179 int_1 = module_0.solve(int_0, int_0) assert int_1 == -1 def test_case_2(): int_0 = 3016 int_1 = module_0.solve(int_0, int_0) assert int_1 == 0 int_2 = 718 int_3 = -1501 int_4 = module_0.solve(int_2, int_3) assert int_4 == -1 ``` - カバレッジ率 ![無理があるだろ](https://hackmd.io/_uploads/HJ-m3aZVR.png) - うまくテスト生成が終了しなかったのでそのせいかも? ## 問題調査 - ARC100~ARC120を調査 - 全119問 - 2問にafter_contest - そのうち1問はなぜ追加されてるのかわからない - after_contestのみWA (TLE) じゃないケースが見つからなかった (コンテスト中のコードにあるのかも) - もう一つは入力の長さが可変 - 要するにテスト自動生成で解決できそうな問題の数はABC以下 ## 所感 - この試みは上手くいかないのではないかという疑念が強くなってきた。 - 切り捨てるなら早めのほうがいい? # 2024/05/27 個人mtgありがとうございました。 ## DOING ### これまでのTODO - どれくらいの割合でafter_contestが足されてるか - どれくらいテスト自動生成で解決できそうか - 実際に自動生成を使う ### テスト自動生成の実験 Pythonのテスト自動生成Pynguinが楽に導入できたので(自身がPythonのコードを一番読めるというのもある)試してみる。 - [ARC176のB問題](https://atcoder.jp/contests/arc176/tasks/arc176_b)でテスト - 正しいコード(after_contest含めてAC)、間違ったコード(after_contest以外AC)を用意してテストを生成。片方のみ通るテストが出てくるか確認 - 上手くいかなかった。 - 考えられる原因 1. ツール 2. そもそもテスト自動生成で検出するのが難しい - カバレッジを見てみる - 100%に到達していても検出できていない - テスト自動生成に夢を持ちすぎか? - 今回の検証はコードの変更部分が少ない - 行けそうだと思ってたけど逆に苦手かも? - あくまで一例なので他に有効な例があるかもしれないが、雲行きが怪しい。 - 正しいコードをほかの人の提出コードにして試してみたが変わらなかった。 - テストの数が多くなれば変わる? - 検証内容 - 正しいコード ```python def solve(N: int, M: int, K: int) -> int: table = [2,4,8,6] if not (1 <= N <= 10**18): return -1 if not (1 <= K < M <= 10**18): return -1 if M-K == 1: return 0 if N >= K else table[(N+3)%4] ##### HERE ##### if N >= M: N -= ((N-M+M-K)//(M-K))*(M-K) return table[(N+3)%4] ``` - 間違ったコード ```python def solve(N: int, M: int, K: int) -> int: table = [2,4,8,6] if not (1 <= N <= 10**18): return -1 if not (1 <= K < M <= 10**18): return -1 if M-K == 1: return 0 if N >= K-1 else table[(N+3)%4] ##### HERE ##### if N >= M: N -= ((N-M+M-K)//(M-K))*(M-K) return table[(N+3)%4] ``` ### コンテスト後にテストケースが追加された例を調査 - ABC186~ABC207を[調査](https://docs.google.com/spreadsheets/d/1dg4JPf-kpMJivf46nK05xAim1WCAt1im7K59pYn98sQ/edit?usp=sharing) - 全132問 - 12問に追加テストケース - 時間制限の検査のための追加テストケースを除くと7個 - コンテスト4回に1回以上の頻度 - しかしそのうちテスト自動生成と相性のよさそうな定数個の入力の問題は僅か2つ - ABC以外のコンテストを含めると変わってくるかも - Javaのコードが全然ないやつもある - こちらから見ても雲行き怪しめ ## TODO - 雲行き怪しめなのでさっさとケリをつける必要がある。 - Evosuiteも試してみる。 - テストをもっといろいろなコードから生成してみる。 - [C++の自動生成](https://github.com/UnitTestBot/UTBotCpp)も視野