diff --git a/fig/paper-formal.png b/fig/paper-formal.png new file mode 100644 index 0000000..1a08b20 Binary files /dev/null and b/fig/paper-formal.png differ diff --git a/fig/paper-my.png b/fig/paper-my.png new file mode 100644 index 0000000..a582e1a Binary files /dev/null and b/fig/paper-my.png differ diff --git a/fig/paper-td-bar.png b/fig/paper-td-bar.png new file mode 100644 index 0000000..435142a Binary files /dev/null and b/fig/paper-td-bar.png differ diff --git a/fig/paper-td.png b/fig/paper-td.png new file mode 100644 index 0000000..ce954d0 Binary files /dev/null and b/fig/paper-td.png differ diff --git a/lecture-test.md b/lecture-test.md index c05f5a8..56c4756 100644 --- a/lecture-test.md +++ b/lecture-test.md @@ -9,48 +9,109 @@ title: SW設計論 #15 # ソフトウェア設計論 ## まつ本 +--- + +# 開発者が知っておくべきトピック集
-実装編- +
前回
+ +・SWEBOK +・良い名前をつける +・コメントはない方が良い +・動くの先にある良いプログラム +・良いプログラムとは? +・_Don't call us, we'll call you_ +・goto不要論からの学び +・できないことを増やす +・Complex vs Complicated +・分割統治 +・DRY・KISS・YAGNI + +--- +# SWEBOK 目次 + +## 全15章 +``` +1. SW要求 +2. SW設計 +3. SW構築 // 前回 +4. SWテスティング // 今日はここ ★★★★ +5. SW保守 +6. SW構成管理 +7. SWエンジニアリング・マネージメント +8. SWエンジニアリングプロセス +9. SWエンジニアリングモデルおよび方法 +10. SW品質 +11. SWエンジニアリング専門技術者実践規律 +12. SWエンジニアリング経済学 +13. 計算基礎 +14. 数学基礎 +15. エンジニアリング基礎 +``` + +--- +# テストは面白い + +## プログラミングと似た面白さ +パズル的な面白さ +自動化の気持ちよさ +実装ほど難しくはないがやはり奥深い + +## ものすごく役立つ +プログラミングを助ける・楽にする技術 +実践的で応用の幅が広い +実装ほど難しくない点もプラス + +## 理論と実践の境界 +様々なセオリーが存在する +勉強するほどうまくなる + --- # 開発者が知っておくべきトピック集
-テスト編-
・SWEBOK +・SWテストの基本 +・手動テストは危険 -・テストはユーザ第1号である +・リファクタリング前にテスト +・先にテストを書く +・_Clean code that works_ ・バグを直す前にテストを落とす -・リファクタリングする前にテストをする + ・テストは証明ではない -・テストをしよう +・テストはユーザ第1号である + --- # SWテスト + 対象SWが意図通り動くかを検証するプロセス 下流工程の一つ, 実装とほぼ1:1 基本はSWを叩いてみて確認する -(コードレビューは叩かずに確かめる方法) +(レビューは叩かずに確かめる方法) -ソートプログラム `sort(arr)` に対するテスト +テストの一例:`sort(arr)` の単体テスト ```java @Test void testSort1() { - actual = sort([1,2,3]); // プログラムを叩いてみる - assert(actual).is([1,2,3]); // その結果が正しいか検証 + actual = sort([1,2,3]); // プログラムを叩いてみて + assert(actual).is([1,2,3]); // その結果を確認する } @Test void testSort2() { - actual = sort([3,1,2]); + actual = sort([2,3,1]); assert(actual).is([1,2,3]); } @Test void testSortNull() { - actual = sort(null); -... + actual = sort([]); // 空配列はどうなるか? + ... ``` --- - # 様々なテスト ## 誰が叩くのか? 人が叩く:マニュアルテスト -機械が叩く:自動テスト (ユニットテスト) +機械が叩く:自動テスト ## どういう目線で叩くか? 中身を意識する:ホワイトボックス @@ -60,9 +121,10 @@ title: SW設計論 #15 機能:単体テスト, 結合テスト, システムテスト 非機能:パフォーマンステスト, 負荷テスト, .. + --- -# テストを書こう -## 実装したら即テストを書く +# テスト作成の流れ + インタフェースを決める ```java List sort(List); @@ -71,24 +133,25 @@ List sort(List); 実装する ```java List sort(List l) { - for .. - .. + for (.. ``` -テストする (実装より先でもOK) +テストを作る ```java @Test void testSort1() { assert(sort([1,2,3])).is([1,2,3]); } ``` -`要求` + `I/F` +要求とIFが決まればテストは作成できる +実装より先にテストを作っても良い --- +# 例:実験スクリプトの場合 インタフェースを決める ```java -python analyze.py in out +python analyze.py in-file out-file ``` 実装する @@ -98,22 +161,335 @@ def analyze(in_file, out_file): .. ``` -テストする (実装より先でもOK) +テストを作る ```python def test_analyze(self): - analyze("test/in.txt", "test/out.txt"); - assert("test/out.txt") .. + analyze("test-in.txt", "tmp.txt"); + assert("tmp.txt").isSameAs("test-out.txt"); +``` + +--- +# 手動テストは危険 + +## よくある良くない手動テスト +```python +print(sort([1,2,3])); +print(sort([3,2,1])); +print(sort([3,2,1,1,1,1,1,1])); +``` + +``` +1,2,3 +1,2,3 +1,1,1,1,1,1,2,3 +``` + +検証にコストを要する, ミスする可能性がある +テストに再利用性がない + - その瞬間の正しさしか確認できていない + - 変更時にまた手動でテストするのか? + +自動化されていないこと自体が問題 + +--- +# 無茶な自動テスト + +```py +if sort([1,2,3]) == [1,2,3]: + print("ok") +else + print("ng") +if sort([3,1,2]) == [1,2,3]: + print("ok") +else + print("ng") +``` + +ここまでやるならテストFWを使おう +テストの共通処理をFWに任す + - テストケースの回収 + - 検証 `assert()` + - `ok` `ng`の回収 + - テストのメタデータ付与 (名前等) + +バグを減らす一つの方法はコードを書かないこと + +--- +# 各種言語のテストFW +## Java:JUnit, TestNG + +```java +@Test @DisplayName("ソート済みデータのソート") +void testSort1() { + List l = sort([1,2,3]); + assertThat(l).equalTo([1,2,3]); +} +``` + +## Python:pytest, unittest +```py +def test_sort1(): + assert sort([1,2,3]) == [1,2,3] +``` + +``` +$ pytest -v +test_sort.py::test_sort1 PASSED [ 33%] +test_sort.py::test_sort2 PASSED [ 66%] +test_sort.py::test_sort3 PASSED [100%] +================= 3 passed in 0.02s ================== +``` + + +--- + +# 開発者が知っておくべきトピック集
-テスト編- +
+ +DUMMY + +--- +# リファクタリング +## 振る舞いを変えずにプログラム内部を改善する +無駄処理の排除, 関数の切り出し, 変数名の修正, .. + +プログラムができたらリファクタリングすべき +良くない状態を放置しない, 負債を貯めない + +![](https://miro.medium.com/v2/resize:fit:828/format:webp/0*u6ejIoMbrZOZxjpf.png) +https://medium.com/@raychongtk/why-is-refactoring-important-2f1e4dec21ab + +--- +# 技術的負債 +
雑談
+ +![](fig/paper-td.png) + +![](fig/paper-td-bar.png) + +--- +# リファクタリングによる破壊 +## 「振る舞いを変えずに」プログラムを改善~ +振る舞いを変えないのが難しい + +すぐに壊れるならまだマシ +新たなバグの混入に気づかないことも + + +## Q. 振る舞いの維持をどう確認すれば良いか? +リファクタリング前と後の等価性を判定したい + +--- +# リファクタリング前にのテスト +## テストを等価性判定の目安に使う +リファクタリング前に必ずテストを用意しておく +テストが通るように内部を改善する + +リファクタリングによってテストが落ちた + → リファクタリング失敗 + +## リファクタリングでIFが変わる場合は? +ラッパーを作っておく + +```diff +- def sort(arr) ++ def sort(arr, is_ascending) +``` + +```py +def sort(arr): + return sort(arr, True) ``` +テストとソースを同時に直さない + +--- +# テストを先に書く +## TDD - Test driven development, テスト駆動開発 +1. まずはテストを書く +2. テストが通るようにプログラムを作る +3. テストを通るようになったらリファクタリング + +テストを自動検証可能な仕様だとみなす + +## ステップ1終了の時点 +```py +def test_sort1(): + assert sort([1,2,3]) == [1,2,3] +def test_sort2(): + assert sort([3,2,1]) == [1,2,3] +def test_sort3(): ... +``` + +```py +def sort(arr): + return None +``` + +--- +# _Clean code that works_ +## TDDのキモ +動くコードと良いコードを分けて考える +分割統治の考え + +まずは動くコードを作る +動いたら良くする + +``` + | + | ★ + Clean | step2 + | + ------------------- + | ↑ + Dirty ☆ → | ★ + step0 | step1 + | + Doesn't work work +``` + + +--- +# 論文でも同じことが言える +
雑談
+ +![](fig/paper-my.png) + +--- +# Test Driven Class +
雑談
+ +## テスト駆動の演習 (演習D) + +学生に仕様とテストを配布する + - 4つのサブ課題, 各課題にテスト20個程度 + +学生は仕様に従いテストが通るように実装する + +全テストが通れば課題達成 + +## Pros +TDD・テストの経験を得られる +TDD・テストの恩恵を自然に受けられる + +教員の手間が減る + +--- +# バグを直す前にテストを落とす +## 予期せぬバグが起きた場合: +1. バグが発生する条件・状況を探す + +```java +sort([3,1,2,-5]); +→ [1,2,3,-5] +``` + + +2. その条件・状況をテストに起こす +プログラムの正しい振る舞いを定義 & 自動検証 +```java +@Test public void testNegative() { + List l = sort([3,1,2,-5]); + assertThat(l).equalTo([-5,1,2,3]); +} +``` + +3. そのテストが通るようにバグを直す + +常にテストとソースを同時に直さない + + + +--- +# バグ報告もテストと同時に +## バグ報告に書くべき項目 +バグの概要 +環境 +バグの再現方法 ★ +期待する振る舞い ★ +実際の振る舞い ★ + +```java +@Test public void testNegative() { + List l = sort([3,1,2,-5]); + assertThat(l).equalTo([-5,1,2,3]); +} +``` + +## 下手な文章よりも役立つ +機械解読可能=客観的・再現可能 + + +--- +# 何でもソースコードにしよう +
雑談
+ +## インストール手順のコード化 +※Home brewのインストールスクリプト +``` +$ /bin/bash -c "$(curl -fsSL https://raw.git../install.sh)" +``` + +## ビルド方法・依存解決のコード化 +Gradle, Bazel +`requirements.txt` + `pip install` + +## 仮想環境のコード化 +Docker + +## インフラ環境のコード化 (IaC) +Chef, Puppet, Pulumi + + +--- + +# 開発者が知っておくべきトピック集
-テスト編- +
+ +DUMMY ---



> Program testing can be used to show the presence of bugs, but never to show their absence -> テストはバグが存在することを示せるが,バグがないことは示せない +> テストはバグが存在することを示せるが, +バグがないことは示せない Edsger W. Dijkstra --- # テストは証明ではない -経済 \ No newline at end of file +経済 + + +--- +# プログラムの正しさの証明? +## 形式手法 +
雑談
+ +数学的・形式的な分析に基づいて信頼性を確認 + +![](fig/paper-formal.png) +E.M. Clarke et al, ACM Computing Surveys 1996. + +ミッションクリティカル分野で利用されている +航空システム・医療等 + + +--- +# サボるために頑張る +投資 + + +--- +# テストの経済学と心理学 + + + +--- +# 手でやらない + +## 手でやるべき状況もある + +--- +# リファクタリング diff --git a/style.css b/style.css index 840e3d0..b41274e 100644 --- a/style.css +++ b/style.css @@ -76,6 +76,14 @@ ul { margin-top: 0; padding-top: 0; } +ol { + padding-inline-start: 45px; +} +u, u::selection { + text-decoration: underline 8pt; + text-decoration-color: #ffc3d6; + text-underline-offset: -3px; +} p, li { @@ -99,9 +107,6 @@ brr { section.outline .disabled { color: hsl(203, 43%, 56%, 60%); } -section.outline .enabledx { -} - section::after { color: #9badbe;