ソフトウェア工学
nユニットテスト
• V字モデル,assert,ユニットテスト,テスト駆動開発,
xUnitフレームワーク,カバレッジ計測,結合テスト
玉木徹(名工大)
テスト
テストとデバッグ
nデバッグ
• コーディングしても
• コンパイルが通らない
• Syntax error
• 実行できない
• 実行時エラー
• Segmentation fault
• Core dump
• 期待した動きをしない
• コードをデバッグする
• 経験と勘を頼りに
nテスト
• コーディングが終わってコードを
実行する
• 実行結果が仕様と一致しているこ
とを確認する
• 異なる場合にはコードを修正す
る
• すべてのモジュールをテストする
• 網羅的に行う
• 系統立てて行う
• 上位モジュールから下位モ
ジュールまですべて
テストとは
n テスト
• 誤りを発見すること
• 誤りが発見されたらテストの意義があ
る
• テストケースを作成する
• ある入力に対して期待する出力
• モジュールの出力が,期待出力に
一致する=テストは成功(テスト
をパスする)
• 出力が期待するものと違うなら誤
り
• 網羅的,体系的に行う
• 勘やなんとなくではダメ
n テスト専門の資格もあるほど
• JSTQB(Japan Software Testing
Qualifications Board,ソフトウェアテ
スト資格認定委員会)
• 国際ソフトウェアテスト資格
Advanced Level (AL) シラバス, 2012
• ALテストマネージャ,ALテストア
ナリスト,ALテクニカルテストア
ナリスト
• 国際ソフトウェアテスト資格
Foundation Level (FL) シラバス, 2012
• FLテスト技術者
V字モデル
nウォーターフォールモデルの拡張
• 各工程にテストを対応させる
要件定義
外部設計
内部設計
実装
テスト
運用・保守
要件定義
外部設計
内部設計
モジュール
実装
単体
テスト
運用・保守
結合
テスト
システム
テスト
受入
テスト
各テスト工程
n 単体テスト(ユニットテスト)
• 1つのモジュールのテスト
• xUnitなどのテストフレームワークを
用いて自動化する
n 結合テスト
• 複数のモジュールを結合して行うテス
ト
n システムテスト
• システム全体として問題なく動作する
かのテスト
• ベンダ側でダミーデータを用いて行う
• 性能テスト,ストレステストや負荷テ
スト,機能テストなども行う
n 受け入れテスト
• クライアントの要求を満たすかどうか
のテスト
• クライアントへの納品も兼ねる場
合がある
• 実際の現場で実際のデータに対してテ
ストする
• 運用テスト,要件テストなどとも言う
場合がある
ユニットテスト
docker composeを利用する
起動する
$ cd 09_01_add_mult
$ docker compose build
$ docker compose up -d
確認する
$ docker compose ps
停止する
$ docker compose down
ユニットテスト
nユニットテスト(単体テスト)
• ユニット(モジュール)が仕様通りになっているかどうかを確認する
n単純な例
• 2つの引数a, bの和と積を計算する関数(モジュール)をテストする
• 設計
• compute.pyの中にmyadd()とmymult()を作成
nテストコードを書く
• 通常のコードとは別にテスト用にコードを書く
• 仕様に従ってモジュールの入出力をテストする
• 適切な引数と期待する出力のセットを用意する(テストケース)
assert:お手軽なテスト方法
n assert文
• 与えた条件を満たさなければ終了
• エラーメッセージも表示
• assert:「断言する」という意味
• 各種言語に存在
n メリット
• 簡単
• 用途多数
• 関数の引数の確認などにも
n デメリット
• いちいち停止してしまう
• 何度もテストを実行するのには向かない
assert文
満たされるべき
条件式
条件式がfalseの
ときに表示され
るメッセージ
和を計算する
自作myadd
assert 3 == myadd(1, 2), '1+2 is not 3'
コードの準備
コードをチェックアウト
$ git checkout assert
import argparse
from compute import myadd
def main():
parser = argparse.ArgumentParser(description='add or multiply two numbers')
parser.add_argument('arg1', type=int,
help='first argment')
parser.add_argument('arg2', type=int,
help='second argment')
parser.add_argument('arg3', choices=['add', 'mult'],
help='add or mult (default: add)')
args = parser.parse_args()
a = args.arg1
b = args.arg2
c = args.arg3
print(a, b, c)
print(myadd(1, 2))
if __name__ == '__main__':
main()
assertを用いたテストコードの例
通常のmyaddを使うコード
main.py
myadd用のテストコード
assert_add.py
ここで自作関数
myaddを使う
myaddをイン
ポート
今は関係のないコード
myaddのテストケース
2種類
myaddをイン
ポート
まだダミー
自作関数はcompute.py
from compute import myadd
def main():
assert 3 == myadd(1, 2), '1+2 is not 3'
assert -2 == myadd(4, -6), '4+(-6) is not -2'
if __name__ == '__main__':
main()
def myadd(a, b):
return 0
assertを用いたテストコードの例
普通にmainを実行
(1+2の結果は
間違っている)
assertのテストを実行
1+2が3になってない
というエラーで終了
$ docker compose exec mypython python main.py 1 2 add
1 2 add
0
$ docker compose exec mypython python assert_add.py
Traceback (most recent call last):
File "/mnt/assert_add.py", line 10, in <module>
main()
File "/mnt/assert_add.py", line 6, in main
assert 3 == myadd(1, 2), '1+2 is not 3'
^^^^^^^^^^^^^^^^
AssertionError: 1+2 is not 3
$
テストフレームワーク
n専用のライブラリやツール
• xUnit:代表的なフレームワーク
• SmallTalk用のSUnitが起源
(1998)
• 各種言語に存在
• Java:JUnit, …
• C/C++:Cutter, CUnit, …
• Python:PyUnit (unittest),
PyTest, …
• xUnit:これらの総称
• xUnitタイプ以外のものも存在する
nフレームワークの利点
• テストが自動化できる
• 何度も実行できる
• テストを再利用できる
• 手間が最小限
• テストの統計が自動計算される
• カバレッジなども意識できる
n使い方
• モジュールのコードを書く
• テスト用のコードを書く
• テストを実行する
TDD:テスト駆動開発
n通常の開発
• 実際のコードを書く
• それからテストコードを書く
• そしてテストを実行する
nTDD (Test-Driven Development)
• 実際のコードは詳しく書かずに作
成(ダミー)
• テストコードを書く
• テストを実行する
• 最初はダミーなのでテストは失
敗する
• テストを成功させるように実際の
コードを書く
• テストが失敗しないとコードを
書かない
• 成功したらテストコードを更新
する
Lifecycle of the Test-Driven Development methodXarawn - Own work
CC BY-SA 4.0
TDD:テスト駆動開発
n利点
• 満たすべき仕様を先にテストコー
ドとして書ける
• 何度もテストを行える
nBDD
• 振る舞い駆動開発(Behavior
Driven Development)
• TDDの派生
• 上位レベルの振る舞いに特化し
たテスト
• (TDDはユニットテスト)
Lifecycle of the Test-Driven Development methodXarawn - Own work
CC BY-SA 4.0
unittestを用いた
ユニットテスト
Python標準unittestパッケージ
ユニットテストの各種フレームワーク
npytestとunittestが上位2
種類
n補足
• None(テストしない)
が40%弱も!
Jet Brains, Python Developers Survey 2021 Results
https://lp.jetbrains.com/python-developers-survey-2021/
unittest:Python標準のフレームワーク
n パッケージの名称はunittest
• xUnitの1つとして説明されるときには
PyUnitと呼ばれることがある
n 構成要素
• ランナー
• テストの実行を管理
• テストケース
• 入力にして期待する結果
• テストスイート
• テストケースの集合
• フィクスチャ
• テストの実行前の準備
• 例:読み書きするファイルやDBの
セットアップ
https://docs.python.org/ja/3/library/unittest.html
コードの準備
コードをチェックアウト
$ git checkout v0.1
import unittest
from compute import myadd
class TestMyCompute(unittest.TestCase):
def test_myadd1(self):
val = myadd(1, 2)
self.assertEqual(3, val)
def test_myadd2(self):
val = myadd(4, 2)
self.assertEqual(6, val)
if __name__ == "__main__":
unittest.main()
テストコードの作成
n テストコードのファ
イル名はtest_add.py
必ずこのクラスから派生すること
和を計算する
自作myadd
クラス名は
分かりやすく
テストケースはメ
ソッド(名前は分
かりやすく) valが3に等
しいとい
うassert
n自作関数はcompute.py まだ
ダミー
unittestを
実行する
2つ目のテス
トケース
valが6に等
しいとい
うassert
注意:メソッド名は
小文字の「test」で始
まるものだけ!(そ
うでないとテストさ
れない)
def myadd(a, b):
return 0
$ docker compose exec mypython python -m test_add
FF
======================================================================
FAIL: test_myadd1 (__main__.TestMyCompute)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/mnt/test_add.py", line 9, in test_myadd1
self.assertEqual(3, val)
AssertionError: 3 != 0
======================================================================
FAIL: test_myadd2 (__main__.TestMyCompute)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/mnt/test_add.py", line 13, in test_myadd2
self.assertEqual(6, val)
AssertionError: 6 != 0
----------------------------------------------------------------------
Ran 2 tests in 0.001s
FAILED (failures=2)
$
テストの実行1
ダミーコード
なので失敗する
失敗したテストの数
実行したテストの
数と実行時間
実行するテストの
ファイル名から.pyを除いた
もの(-mはモジュールを実
行するという意味)
$ docker compose exec mypython python -m unittest test_add.py
FF
======================================================================
FAIL: test_myadd1 (test_add.TestMyCompute)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/mnt/test_add.py", line 9, in test_myadd1
self.assertEqual(3, val)
AssertionError: 3 != 0
======================================================================
FAIL: test_myadd2 (test_add.TestMyCompute)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/mnt/test_add.py", line 13, in test_myadd2
self.assertEqual(6, val)
AssertionError: 6 != 0
----------------------------------------------------------------------
Ran 2 tests in 0.001s
FAILED (failures=2)
$
テストの実行2
実行するテストの
ファイル名
ダミーコード
なので失敗する
失敗したテストの数
実行したテストの
数と実行時間
TDDを実践
仕様に基づいてテストケースを追加
n以下は架空の内部設計におけるmyadd(a, b)の仕様
• aとbが両方正なら,結果a+bは正
• aとbが両方負なら,結果a+bは負
• aが0なら,結果a+bはb
• bが0なら,結果a+bはa
• aが正,bが負なら
• a > bなら結果a+bは正
• a < bなら結果a+bは負
内部設計の段階で
気がつくべき問題が...
コードの準備
コードをチェックアウト
$ git checkout v0.2
テストコードを作成
n作成するテストコードに対応する仕様
• aとbが両方正なら,結果a+bは正
• aとbが両方負なら,結果a+bは負
条件がTrueである
というassert
条件がTrueである
というassert
class TestMyAdd(unittest.TestCase):
def test_add(self):
val = myadd(1, 2)
self.assertEqual(3, val)
def test_add_pp(self):
val = myadd(2, 6)
self.assertTrue(val > 0)
def test_add_nn(self):
val = myadd(-2, -6)
self.assertTrue(val < 0)
テストを実行:失敗
nコードを書いていない
• 失敗するべき
• 失敗したからコードを書ける
$ docker compose exec mypython python -m unittest test_add.py
FFF
======================================================================
FAIL: test_add (test_add.TestMyAdd)
...
----------------------------------------------------------------------
Ran 3 tests in 0.001s
FAILED (failures=3)
$
テストを成功するようにコードを書く
n作成するコードに対応する仕様
• aとbが両方正なら,結果a+bは正
• aとbが両方負なら,結果a+bは負
aとbが正の場合
aとbが負の場合
補足:
ここでは仕様に忠実に実装している
(単なる和の計算だけど)
def myadd(a, b):
if a > 0 and b > 0:
return a + b
if a < 0 and b < 0:
return a + b
return 0
$ docker compose exec mypython python -m unittest test_add.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
$
テストを実行:成功
nコードを書いた
• テストは成功するべき
• 失敗するならデバッグして修正
3つのテストケース
すべて成功
テストコードを追加
n追加するテストコードに対応する仕様
• aが0なら,結果a+bはb
• bが0なら,結果a+bはa
a=0の場合
b=0の場合
def test_add(self):
val = myadd(1, 2)
self.assertEqual(3, val)
def test_add_pp(self):
val = myadd(2, 6)
self.assertTrue(val > 0)
def test_add_nn(self):
val = myadd(-2, -6)
self.assertTrue(val < 0)
def test_add_a0(self):
val = myadd(0, 6)
self.assertEqual(6, val)
def test_add_b0(self):
val = myadd(2, 0)
self.assertTrue(2, val)
$ docker compose exec mypython python -m unittest test_add.py
.F...
======================================================================
FAIL: test_add_a0 (test_add.TestMyAdd)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/mnt/test_add.py", line 21, in test_add_a0
self.assertEqual(6, val)
AssertionError: 6 != 0
----------------------------------------------------------------------
Ran 5 tests in 0.001s
FAILED (failures=1)
$
再度テストを実行:失敗
nコードを書いていない
• 失敗するべき
• 失敗したからコードを書ける
テストに失敗
テストを成功するようにコードを追加
n作成するコードに対応する仕様
• aが0なら,結果a+bはb
• bが0なら,結果a+bはa
補足:
ここでは仕様に忠実に実
装している
(単なる和の計算だけ
ど)
a=0の場合
b=0の場合
def myadd(a, b):
if a > 0 and b > 0:
return a + b
if a < 0 and b < 0:
return a + b
if a == 0:
return b
if b == 0:
return a
return 0
再度テストを実行:成功
nコードを書いた
• テストは成功するべき
• 失敗するならデバッグして修正
5つのテストケース
すべて成功
$ docker compose exec mypython python -m unittest test_add.py
.....
----------------------------------------------------------------------
Ran 5 tests in 0.000s
OK
$
テストコードを追加
n追加するテストコードに対応する仕様
• aが正,bが負なら
• a > bなら結果a+bは正
• a < bなら結果a+bは負
a > bの場合
a < bの場合
def test_add_a0(self):
val = myadd(0, 6)
self.assertEqual(6, val)
def test_add_b0(self):
val = myadd(2, 0)
self.assertTrue(2, val)
def test_add_pn_a_gt_b(self):
val = myadd(4, -1)
self.assertTrue(val > 0)
def test_add_pn_a_lt_b(self):
val = myadd(-1, 4)
self.assertTrue(val < 0)
再度テストを実行:失敗
nコードを書いていない
• 失敗するべき
• 失敗したからコードを書ける
$ docker compose exec mypython python -m unittest test_add.py
....FF.
======================================================================
FAIL: test_add_pn_a_gt_b (test_add.TestMyAdd)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/mnt/test_add.py", line 29, in test_add_pn_a_gt_b
self.assertTrue(val > 0)
AssertionError: False is not true
...
----------------------------------------------------------------------
Ran 7 tests in 0.002s
FAILED (failures=2)
$
テストを成功するようにコードを追加
n作成するコードに対応する仕様
• aが正,bが負なら
• a > bなら結果a+bは正
• a < bなら結果a+bは負
補足:
ここでは仕様に忠実に実
装している
(単なる和の計算だけ
ど)
def myadd(a, b):
if a > 0 and b > 0:
return a + b
if a < 0 and b < 0:
return a + b
if a == 0:
return b
if b == 0:
return a
if a > 0 and b < 0:
if a > b:
return a + b
if a < b:
return a + b
return 0
a > bの場合
a < bの場合
$ docker compose exec mypython python -m unittest test_add.py
.....F.
======================================================================
FAIL: test_add_pn_a_lt_b (test_add.TestMyAdd)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/mnt/test_add.py", line 33, in test_add_pn_a_lt_b
self.assertTrue(val < 0)
AssertionError: False is not true
----------------------------------------------------------------------
Ran 7 tests in 0.001s
FAILED (failures=1)
$
再度テストを実行:成功...あれ?
nコードを書いた
• テストは成功するべき
• 失敗するならデバッグして修正...?
a < bの場合が
失敗している
内部設計の仕様を再確認
n以下は架空の内部設計におけるmyadd(a, b)の仕様
• aとbが両方正なら,結果a+bは正
• aとbが両方負なら,結果a+bは負
• aが0なら,結果a+bはb
• bが0なら,結果a+bはa
• aが正,bが負なら
• a > bなら結果a+bは正
• a < bなら結果a+bは負
n内部設計のミス.正しくは
• aが正,bが負なら
• |a| > |b|なら結果a+bは正
• |a| < |b|なら結果a+bは負
a > 0, b < 0だから
「a < bの場合」は
ありえない!
テストコードの書き直し
&
コードの書き直し
手戻り
が発生
コードとテストコードの修正
diff --git a/test_add.py b/test_add.py
index ed754ff..a16ec50 100644
--- a/test_add.py
+++ b/test_add.py
@@ -29,7 +29,7 @@ class TestMyAdd(unittest.TestCase):
self.assertTrue(val > 0)
def test_add_pn_a_lt_b(self):
- val = myadd(-1, 4)
+ val = myadd(1, -4)
self.assertTrue(val < 0)
diff --git a/compute.py b/compute.py
index 97424a8..dc5fd2b 100644
--- a/compute.py
+++ b/compute.py
@@ -13,9 +13,9 @@ def myadd(a, b):
return a
if a > 0 and b < 0:
- if a > b:
+ if abs(a) > abs(b):
return a + b
- if a < b:
+ if abs(a) < abs(b):
return a + b
return 0
再度テストを実行:成功
nコードを書いた
• テストは成功するべき
• 失敗するならデバッグして修正(を繰り返す)
$ docker compose exec mypython python -m unittest test_add.py
.......
----------------------------------------------------------------------
Ran 7 tests in 0.000s
OK
$
カバレッジの計算
カバレッジとは
n ホワイトボックステストの指標
• カバレッジ
• 全体に占めるテストした部分の割
合
• 網羅性の指標
• 命令網羅(C0)
• すべての命令を1回以上実行
• 分岐網羅(C1)
• すべての分岐の真と偽をそれ
ぞれ1回以上実行
• 条件網羅(C2)
• すべての分岐の組み合わせ
(経路)を1回以上実行
n コードカバレッジ
• C0網羅率
• 実行した命令数/全命令数
• C0カバレッジ
• C1網羅率
• 実行した分岐数/全分岐数
• C1カバレッジ
• C2網羅率
• 実行した経路数/全経路数
• C2カバレッジ
コードの準備
コードをチェックアウト
$ git checkout main
$ docker compose exec mypython python -m unittest test_add.py
.......
----------------------------------------------------------------------
Ran 7 tests in 0.000s
OK
$ docker compose exec mypython coverage run test_add.py
.......
----------------------------------------------------------------------
Ran 7 tests in 0.000s
OK
$ ls -la .coverage
-rw-r--r-- 1 tamaki staff 53248 6 13 17:24 .coverage
$
coverageコマンドによる計測
カバレッジの実行
テストは成功している
実行するファイル
(テストコードに
限らない)
このファイル内に
計測結果が保存されている
カバレッジ計測結果の表示
カバレッジ計測結果の表示
網羅率
実行され
た行数
実行され
てない行
数
$ docker compose exec mypython coverage report
Name Stmts Miss Cover
---------------------------------
compute.py 29 14 52%
test_add.py 26 0 100%
---------------------------------
TOTAL 55 14 75%
$
カバレッジ計測結果をHTMLで表示
カバレッジ計測結果のHTMLを作成
htlmcov/index.htmlを
ブラウザで見れば良い
$ docker compose exec mypython coverage html
Wrote HTML report to htmlcov/index.html
$ ls htmlcov
compute_py.html favicon_32.png keybd_closed.png status.json test_add_py.html
coverage_html.js index.html keybd_open.png style.css
$
カバレッジ計測結果をHTMLで表示
これがhtlmcov/index.html
compute.pyを
クリックすると
この行が実行されて
いないことが分かる
テストスイート
複数のテストケースを一括で実行
複数のテストケースを作成
nそれぞれのモジュールに
対して作成
• myadd()に対して
test_add.py
• mymult()に対して
test_mult.py
n一括で実行したい
• テストコードが増えると煩
雑
• テストし忘れが発生
nテストスイート
• 複数のテストケースの集合
$ docker compose exec mypython python -m unittest test_add.py
.......
-------------------------------------------------------------------
Ran 7 tests in 0.001s
OK
$ docker compose exec mypython python -m unittest test_mult.py
.......
-------------------------------------------------------------------
Ran 7 tests in 0.001s
OK
$
$ docker compose exec mypython python -m unittest test_compute.py
..............
----------------------------------------------------------------------
Ran 14 tests in 0.002s
OK
$
テストスイートの作成とテストの実行
test_compute.pyを
用意する 各テストコードの
ファイル中のクラス
をインポート
インポートしたテス
トをすべて実行
これをテスト
すると
test_add.pyの7個+
test_mult.pyの7個=合計14
個のテストを実行
import unittest
from test_add import TestMyAdd
from test_mult import TestMyMult
if __name__ == "__main__":
unittest.main()
$ docker compose exec mypython coverage run test_compute.py
..............
----------------------------------------------------------------------
Ran 14 tests in 0.002s
OK
$ docker compose exec mypython coverage report
Name Stmts Miss Cover
-------------------------------------
compute.py 29 2 93%
test_add.py 26 1 96%
test_compute.py 5 0 100%
test_mult.py 26 1 96%
-------------------------------------
TOTAL 86 4 95%
$
テストスイートのカバレッジ
カバレッジも
一度に計算
test_add.pyの7個+
test_mult.pyの7個=合計14
個のテストを実行
VScode連携
Git:テスト毎にコミットすること!
VScodeでのunittest実行
testタブから
このテストファイ
ルを実行
すべてのテスト
ファイルを実行
VScodeでのunittest実行
成功したテスト
成功したテスト
結合テスト
結合テストとは
nおさらい
• 単体テスト(ユニットテスト)
• 1つのモジュールのテスト
• xUnitなどのテストフレームワークを用いて自動化する
• 結合テスト
• 複数のモジュールを結合して行うテスト
n上位モジュールをxUnitフレームワークでテストする
• 上位からテストするので,トップダウンテスト
• そのためにはスタブ(モック)が必要
• unittestフレームワークのMagicMock()を利用する
結合テストの方式
nトップダウンテスト
• 上位モジュールを先に作成・テストする
• 未作成の下位モジュールにはダミー(スタブ)を用いる
nボトムアップテスト
• 下位モジュールを先に作成・テストする
• 未作成の上位モジュールはダミー(ドライバ)を用いる
nサンドイッチテスト
• ボトムアップとトップダウンを並行して行うテスト
モジュール
スタブ スタブ スタブ
ドライバ
モジュール1 モジュール2 モジュール3
上位モジュールの仕様例
n以下は架空の内部設計の仕様
• クラス名:AddOrMult
• コンストラクタに与えるもの
• 和計算関数myadd
• 積計算関数mymult
• メソッド名:do(a, b, c)
• 引数cが文字列’add’ならa+b
を返す
• 引数cが文字列’mult’ならa*b
を返す
n前提
• myaddとmymultはまだ「実装して
いない」
• この段階でAddOrMultをテストす
る
• これは上位モジュールの「ユ
ニットテスト」
• まだ結合テストではない
• myaddとmymultが実装でき
たら結合テストを行う
n用意するもの
• myaddとmymultのスタブ
• モック(mock)とも言う
コードの準備
コードをチェックアウト
$ git checkout main
上位モジュールのコード例
クラス名
コンストラクタ
引数はselfを除いて2つ
与えられた2つの関数を
クラスメンバ(self)と
して記憶しておく
メソッドdoは仕様通り
記憶しておいたself.addと
self.multを使う
integration.py
class AddOrMult():
def __init__(self, add_func, mult_func):
super().__init__()
self.add = add_func
self.mult = mult_func
def do(self, a, b, c):
if c == 'add':
return self.add(a, b)
if c == 'mult':
return self.mult(a, b)
return None
テストコード
実装前なの
で呼び出せ
ない
unittestの
mockを利
用
テストする
上位モ
ジュール
test_integration.py
import unittest
# from compute import myadd, mymult
from unittest.mock import MagicMock
myadd = MagicMock()
mymult = MagicMock()
from integration import AddOrMult
def is_mock(obj):
return obj.__class__.__name__ == 'MagicMock'
class TestAddOrMult(unittest.TestCase):
def test_add(self):
if is_mock(myadd):
myadd.return_value = 8
add_or_mult = AddOrMult(myadd, mymult)
val = add_or_mult.do(2, 6, 'add')
self.assertEqual(8, val)
def test_mult(self):
if is_mock(mymult):
mymult.return_value = 12
add_or_mult = AddOrMult(myadd, mymult)
val = add_or_mult.do(2, 6, 'mult')
self.assertEqual(12, val)
if __name__ == "__main__":
unittest.main()
import unittest
# from compute import myadd, mymult
from unittest.mock import MagicMock
myadd = MagicMock()
mymult = MagicMock()
from integration import AddOrMult
テストコード
準備:
mockかどうか
クラス名で判定する
関数を作っておく
test_integration.py
import unittest
# from compute import myadd, mymult
from unittest.mock import MagicMock
myadd = MagicMock()
mymult = MagicMock()
from integration import AddOrMult
def is_mock(obj):
return obj.__class__.__name__ == 'MagicMock'
class TestAddOrMult(unittest.TestCase):
def test_add(self):
if is_mock(myadd):
myadd.return_value = 8
add_or_mult = AddOrMult(myadd, mymult)
val = add_or_mult.do(2, 6, 'add')
self.assertEqual(8, val)
def test_mult(self):
if is_mock(mymult):
mymult.return_value = 12
add_or_mult = AddOrMult(myadd, mymult)
val = add_or_mult.do(2, 6, 'mult')
self.assertEqual(12, val)
if __name__ == "__main__":
unittest.main()
def is_mock(obj):
return obj.__class__.__name__ == 'MagicMock'
テストコード
mockの返
り値を8に
指定
ここは普通にユニット
テストするだけ
もしmockなら,関数myaddは未実装
なので,テスト用にここで強制的に
myaddの返り値を設定してしまう
test_integration.py
import unittest
# from compute import myadd, mymult
from unittest.mock import MagicMock
myadd = MagicMock()
mymult = MagicMock()
from integration import AddOrMult
def is_mock(obj):
return obj.__class__.__name__ == 'MagicMock'
class TestAddOrMult(unittest.TestCase):
def test_add(self):
if is_mock(myadd):
myadd.return_value = 8
add_or_mult = AddOrMult(myadd, mymult)
val = add_or_mult.do(2, 6, 'add')
self.assertEqual(8, val)
def test_mult(self):
if is_mock(mymult):
mymult.return_value = 12
add_or_mult = AddOrMult(myadd, mymult)
val = add_or_mult.do(2, 6, 'mult')
self.assertEqual(12, val)
if __name__ == "__main__":
unittest.main()
class TestAddOrMult(unittest.TestCase):
def test_add(self):
if is_mock(myadd):
myadd.return_value = 8
add_or_mult = AddOrMult(myadd, mymult)
val = add_or_mult.do(2, 6, 'add')
self.assertEqual(8, val)
$ docker compose exec mypython python -m unittest test_integration.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
$ docker compose exec mypython coverage run test_integration.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
$ docker compose exec mypython coverage report
Name Stmts Miss Cover
-----------------------------------------
integration.py 11 1 91%
test_integration.py 22 0 100%
-----------------------------------------
TOTAL 33 1 97%
$
ユニットテストの実行
テストを実
行
カバレッジ
計測
下位モ
ジュールは
使われてい
ない
結合テスト
n下位モジュールを作成したら結合テスト
モックから 実際の下位モジュールに変更
(後は修正なし)
test_integration.py test_integration.py
import unittest
from compute import myadd, mymult
# from unittest.mock import MagicMock
# myadd = MagicMock()
# mymult = MagicMock()
from integration import AddOrMult
import unittest
# from compute import myadd, mymult
from unittest.mock import MagicMock
myadd = MagicMock()
mymult = MagicMock()
from integration import AddOrMult
結合テストの実行
テストを実
行
カバレッジ
計測
下位モ
ジュールを
使っている
$ docker compose exec mypython python -m unittest test_integration.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
$ docker compose exec mypython coverage run test_integration.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
$ docker compose exec mypython coverage report
Name Stmts Miss Cover
-----------------------------------------
compute.py 29 23 21%
integration.py 11 1 91%
test_integration.py 20 2 90%
-----------------------------------------
TOTAL 60 26 57%
$
pytest:もう一つの方法
unittest以外にもテストフレームワークがあります
pytestは代表的なものの一つです
pytest:Pythonのフレームワーク
nメジャーなテストフレー
ムワーク
• 標準のunittestよりも有名
• unittestよりもシンプル
n簡単な使い方
• pytest スクリプト名.py
• そのテストスクリプトを
pytestで実行
• pytest
• ディレクトリ内にある
「test_***.py」をすべて
実行
https://docs.pytest.org/
コードの準備
コードをチェックアウト
$ git checkout pytest
テストの書き方1
nassertでテスト
• 対象のモジュールが満
たすべき内容をassert
で列挙
• pytestはスクリプト内
の「test_***()」と
いう名前の関数を選択
実行
myadd()が
テスト対象
test_myadd()が
pytestで実行される
import pytest
def myadd(a, b):
if a > 0 and b > 0:
return a + b
if a < 0 and b < 0:
return a + b
if a == 0:
return b
if b == 0:
return a
if a > 0 and b < 0:
if abs(a) > abs(b):
return a + b
if abs(a) < abs(b):
from compute import myadd, mymult
def test_myadd():
assert myadd(1, 2) == 3
assert myadd(2, 6) > 0
assert myadd(-2, -6) < 0
assert myadd(0, 6) == 6
assert myadd(2, 0) == 2
assert myadd(4, -1) > 0
assert myadd(1, -4) < 0
pytestの実行:成功
npytestにスクリプト名
を与える
• オプション「-v」:テス
トを詳しく表示
$ docker compose exec mypython pytest test_compute1.py
===================== test session starts =====================
platform linux -- Python 3.10.4, pytest-7.1.2, pluggy-1.0.0
rootdir: /mnt
collected 2 items
test_compute1.py .. [100%]
====================== 2 passed in 0.01s ======================
$ docker compose exec mypython pytest -v test_compute1.py
===================== test session starts =====================
platform linux -- Python 3.10.4, pytest-7.1.2, pluggy-1.0.0 --
/usr/local/bin/python
cachedir: .pytest_cache
rootdir: /mnt
collected 2 items
test_compute1.py::test_myadd PASSED [ 50%]
test_compute1.py::test_mymult PASSED [100%]
====================== 2 passed in 0.01s ======================
$
$ docker compose exec mypython pytest -v test_compute1.py
===================== test session starts =====================
rootdir: /mnt
collected 2 items
test_compute1.py::test_myadd PASSED [ 50%]
test_compute1.py::test_mymult FAILED [100%]
========================== FAILURES ===========================
_________________________ test_mymult _________________________
def test_mymult():
assert mymult(2, 6) == 12
assert mymult(2, 6) > 0
assert mymult(-2, -6) > 0
assert mymult(2, -6) < 0
assert mymult(-2, 6) < 0
assert mymult(0, 6) == 0
> assert mymult(2, 0) == 1
E assert 0 == 1
E + where 0 = mymult(2, 0)
test_compute1.py:23: AssertionError
=================== short test summary info ===================
FAILED test_compute1.py::test_mymult - assert 0 == 1
================= 1 failed, 1 passed in 0.04s =================
$
pytestの実行:失敗
n失敗すると停止
• assert文が失敗した以降
は,そのスクリプトは実
行されていない
• 他にも失敗するかもしれ
ないが,それは不明
ここでわざと
失敗させてみた
from compute import myadd, mymult
def test_myadd():
assert myadd(1, 2) == 3
assert myadd(2, 6) > 0
assert myadd(-2, -6) < 0
assert myadd(0, 6) == 6
assert myadd(2, 0) == 2
assert myadd(4, -1) > 0
assert myadd(1, -4) < 0
import pytest
from compute import myadd, mymult
@pytest.mark.parametrize(
("a", "b", "c"), [
(1, 2, 3),
(0, 6, 6),
(2, 0, 2),
])
def test_myadd_eq(a, b, c):
assert myadd(a, b) == c
@pytest.mark.parametrize(
("a", "b"), [
(-2, -6),
(1, -4),
])
def test_myadd_lt(a, b):
assert myadd(a, b) < 0
テストの書き方2
nテストケースを別で用
意する
• 「パラメトライズ」と
呼ぶ
• テストケースそれぞれ
でテスト関数を実行
• assertで失敗しても,
次のテストケースへテ
ストを継続する
引数を
別で記述
テスト関数内に
すべて列挙
テスト関数内には
一つのテスト
pytestの実行:成功
n失敗しても継続
• すべての失敗が一度に
検出できる
$ docker compose exec mypython pytest -v test_compute2.py
===================== test session starts =====================
platform linux -- Python 3.10.4, pytest-7.1.2, pluggy-1.0.0 --
/usr/local/bin/python
cachedir: .pytest_cache
rootdir: /mnt
collected 14 items
test_compute2.py::test_myadd_eq[1-2-3] PASSED [ 7%]
test_compute2.py::test_myadd_eq[0-6-6] PASSED [ 14%]
test_compute2.py::test_myadd_eq[2-0-2] PASSED [ 21%]
test_compute2.py::test_myadd_lt[-2--6] PASSED [ 28%]
test_compute2.py::test_myadd_lt[1--4] PASSED [ 35%]
test_compute2.py::test_myadd_gt[2-6] PASSED [ 42%]
test_compute2.py::test_myadd_gt[4--1] PASSED [ 50%]
test_compute2.py::test_mymult_eq[2-6-12] PASSED [ 57%]
test_compute2.py::test_mymult_eq[0-6-0] PASSED [ 64%]
test_compute2.py::test_mymult_eq[2-0-0] PASSED [ 71%]
test_compute2.py::test_mymult_lt[2--6] PASSED [ 78%]
test_compute2.py::test_mymult_lt[-2-6] PASSED [ 85%]
test_compute2.py::test_mymult_gt[2-6] PASSED [ 92%]
test_compute2.py::test_mymult_gt[-2--6] PASSED [100%]
===================== 14 passed in 0.02s ======================
$
$ docker compose exec mypython pytest -v test_compute2.py
===================== test session starts =====================
platform linux -- Python 3.10.4, pytest-7.1.2, pluggy-1.0.0 --
/usr/local/bin/python
cachedir: .pytest_cache
rootdir: /mnt
collected 14 items
test_compute2.py::test_myadd_eq[1-2-3] PASSED [ 7%]
test_compute2.py::test_myadd_eq[0-6-6] PASSED [ 14%]
test_compute2.py::test_myadd_eq[2-0-1] FAILED [ 21%]
test_compute2.py::test_myadd_lt[-2--6] PASSED [ 28%]
test_compute2.py::test_myadd_lt[1--4] PASSED [ 35%]
test_compute2.py::test_myadd_gt[2-6] PASSED [ 42%]
test_compute2.py::test_myadd_gt[4--1] PASSED [ 50%]
test_compute2.py::test_mymult_eq[2-6-12] PASSED [ 57%]
test_compute2.py::test_mymult_eq[0-6-0] PASSED [ 64%]
test_compute2.py::test_mymult_eq[2-0-0] PASSED [ 71%]
test_compute2.py::test_mymult_lt[2--6] PASSED [ 78%]
test_compute2.py::test_mymult_lt[-2-6] PASSED [ 85%]
test_compute2.py::test_mymult_gt[2-6] PASSED [ 92%]
test_compute2.py::test_mymult_gt[-2--6] PASSED [100%]
...
pytestの実行:失敗
n失敗しても継続
• すべての失敗が一度に
検出できる
失敗したテスト
...
========================== FAILURES ===========================
____________________ test_myadd_eq[2-0-1] _____________________
a = 2, b = 0, c = 1
@pytest.mark.parametrize(
("a", "b", "c"), [
(1, 2, 3),
(0, 6, 6),
(2, 0, 1),
])
def test_myadd_eq(a, b, c):
> assert myadd(a, b) == c
E assert 2 == 1
E + where 2 = myadd(2, 0)
test_compute2.py:13: AssertionError
=================== short test summary info ===================
FAILED test_compute2.py::test_myadd_eq[2-0-1] - assert 2 == 1
================ 1 failed, 13 passed in 0.05s =================
$
pytestの実行:失敗
n失敗しても継続
• すべての失敗が一度に
検出できる
ここでわざと
失敗させてみた
VScodeでのpytest実行
testタブから
このテストファイ
ルを実行
すべてのテスト
ファイルを実行
VScodeでのpytest実行
成功したテスト
成功したテスト
ソフトウェア工学2023 11 テスト
課題
nunittestを用いたテストコードを
TDDで作成せよ
• main関数から自作関数を呼び出す
• 関数は複数の引数から何かを計
算し値を返すものとする
• 例:差・商・最大・最小・総
和・冪乗・文字列連結など
• 何か1つの機能なら,ほかの
ものでも良い
• その関数の仕様を5つ程度決める
(内部設計)
• 自作関数は,最初はダミーコードの
みを記述
する
• それぞれの仕様毎に
• テストが失敗することを確認す
る
• 自作関数の,対応する仕様の部
分を記述する
• テストが成功することを確認す
る
• テスト成功毎にgit commitする
nテストコードのカバレッジを計算
せよ
• reportの出力,htmlの出力を確認す
る
想定試験問題
nユニットテストとは何か,その重要性を説明せよ
nassert文とは何か,その使い方を説明せよ
nテストフレームワークとはどのようなものか,その利点も説明せよ
nテストケースとは何かを,具体例で説明せよ
nPythonのxUnitフレームワークunittestの使い方を説明せよ
nTDDとは何か,実践方法と,その利点を説明せよ
nテストスイートとは何かを説明せよ
nカバレッジとは何か,その種類を説明せよ

More Related Content

PPTX
独立低ランク行列分析に基づく音源分離とその発展
PDF
空気/体内伝導マイクロフォンを用いた雑音環境下における自己発声音強調/抑圧法
PDF
音情報処理における特徴表現
PPTX
基底変形型教師ありNMFによる実楽器信号分離 (in Japanese)
PDF
音素事後確率を利用した表現学習に基づく発話感情認識
PDF
CTCに基づく音響イベントからの擬音語表現への変換
PDF
Neural text-to-speech and voice conversion
PPTX
音源分離における音響モデリング(Acoustic modeling in audio source separation)
独立低ランク行列分析に基づく音源分離とその発展
空気/体内伝導マイクロフォンを用いた雑音環境下における自己発声音強調/抑圧法
音情報処理における特徴表現
基底変形型教師ありNMFによる実楽器信号分離 (in Japanese)
音素事後確率を利用した表現学習に基づく発話感情認識
CTCに基づく音響イベントからの擬音語表現への変換
Neural text-to-speech and voice conversion
音源分離における音響モデリング(Acoustic modeling in audio source separation)

What's hot (20)

PDF
UnityのMultiplayサービスの得意な事
PPTX
半教師あり非負値行列因子分解における音源分離性能向上のための効果的な基底学習法
PDF
ソフト高速化の専門家が教える!AI・IoTエッジデバイスの選び方
PDF
CREST「共生インタラクション」共創型音メディア機能拡張プロジェクト
PDF
音声感情認識の分野動向と実用化に向けたNTTの取り組み
PDF
独立深層学習行列分析に基づく多チャネル音源分離(Multichannel audio source separation based on indepen...
PDF
音楽波形データからコードを推定してみる
PDF
サブバンドフィルタリングに基づくリアルタイム広帯域DNN声質変換の実装と評価
PDF
時間領域低ランクスペクトログラム近似法に基づくマスキング音声の欠損成分復元
PDF
Deep Neural Networkに基づく日常生活行動認識における適応手法
PDF
雑音環境下音声を用いた音声合成のための雑音生成モデルの敵対的学習
PDF
音声合成のコーパスをつくろう
PPTX
独立性に基づくブラインド音源分離の発展と独立低ランク行列分析 History of independence-based blind source sep...
PDF
プログラマ目線から見たRDMAのメリットと その応用例について
PPTX
非負値行列因子分解に基づくブラインド及び教師あり音楽音源分離の効果的最適化法
PDF
距離学習を導入した二値分類モデルによる異常音検知
ODP
音声生成の基礎と音声学
PDF
環境音の特徴を活用した音響イベント検出・シーン分類
PPTX
実践的Reaper勉強会
PPTX
音楽信号処理における基本周波数推定を応用した心拍信号解析
UnityのMultiplayサービスの得意な事
半教師あり非負値行列因子分解における音源分離性能向上のための効果的な基底学習法
ソフト高速化の専門家が教える!AI・IoTエッジデバイスの選び方
CREST「共生インタラクション」共創型音メディア機能拡張プロジェクト
音声感情認識の分野動向と実用化に向けたNTTの取り組み
独立深層学習行列分析に基づく多チャネル音源分離(Multichannel audio source separation based on indepen...
音楽波形データからコードを推定してみる
サブバンドフィルタリングに基づくリアルタイム広帯域DNN声質変換の実装と評価
時間領域低ランクスペクトログラム近似法に基づくマスキング音声の欠損成分復元
Deep Neural Networkに基づく日常生活行動認識における適応手法
雑音環境下音声を用いた音声合成のための雑音生成モデルの敵対的学習
音声合成のコーパスをつくろう
独立性に基づくブラインド音源分離の発展と独立低ランク行列分析 History of independence-based blind source sep...
プログラマ目線から見たRDMAのメリットと その応用例について
非負値行列因子分解に基づくブラインド及び教師あり音楽音源分離の効果的最適化法
距離学習を導入した二値分類モデルによる異常音検知
音声生成の基礎と音声学
環境音の特徴を活用した音響イベント検出・シーン分類
実践的Reaper勉強会
音楽信号処理における基本周波数推定を応用した心拍信号解析
Ad

Similar to ソフトウェア工学2023 11 テスト (20)

PPT
ユニットテスト_2日目
PDF
エクストリームエンジニア4
KEY
Unit testで定時帰宅!
PDF
最近の単体テスト
PDF
Tokyor14 - R言語でユニットテスト
PPT
ユニットテスト 1日目
PDF
はこだてIKA 第4回勉強会 単体テスト
PDF
第4回勉強会 単体テストのすすめ
PDF
Akka Unit Testing
KEY
JUnit実践入門 xUnitTestPatternsで学ぶユニットテスト
KEY
テストコードのリファクタリング
PDF
xUTP Chapter19 (2). Testcase Class
PDF
xUnit Test Patterns - Chapter19
PDF
Introduction to Spock
PDF
JS開発におけるTDDと自動テストツール利用の勘所
PDF
C# から java へのプログラム移植で体験したtddの効果は?
KEY
テスト初心者Androiderのためのソフトウェアテスト入門
PDF
テスト 【クラウドアプリケーションのためのオブジェクト指向分析設計講座 第33回】
PDF
Ruby初級者向けレッスン 第46回 ─── Test::Unit
PPTX
Junit4
ユニットテスト_2日目
エクストリームエンジニア4
Unit testで定時帰宅!
最近の単体テスト
Tokyor14 - R言語でユニットテスト
ユニットテスト 1日目
はこだてIKA 第4回勉強会 単体テスト
第4回勉強会 単体テストのすすめ
Akka Unit Testing
JUnit実践入門 xUnitTestPatternsで学ぶユニットテスト
テストコードのリファクタリング
xUTP Chapter19 (2). Testcase Class
xUnit Test Patterns - Chapter19
Introduction to Spock
JS開発におけるTDDと自動テストツール利用の勘所
C# から java へのプログラム移植で体験したtddの効果は?
テスト初心者Androiderのためのソフトウェアテスト入門
テスト 【クラウドアプリケーションのためのオブジェクト指向分析設計講座 第33回】
Ruby初級者向けレッスン 第46回 ─── Test::Unit
Junit4
Ad

More from Toru Tamaki (20)

PDF
論文紹介:Unboxed: Geometrically and Temporally Consistent Video Outpainting
PDF
論文紹介:OVO-Bench: How Far is Your Video-LLMs from Real-World Online Video​ Unde...
PDF
論文紹介:HOTR: End-to-End Human-Object Interaction Detection​ With Transformers, ...
PDF
論文紹介:Segment Anything, SAM2: Segment Anything in Images and Videos
PDF
論文紹介:Unbiasing through Textual Descriptions: Mitigating Representation Bias i...
PDF
論文紹介:AutoPrompt: Eliciting Knowledge from Language Models with Automatically ...
PDF
論文紹介:「Amodal Completion via Progressive Mixed Context Diffusion」「Amodal Insta...
PDF
論文紹介:「mPLUG-Owl3: Towards Long Image-Sequence Understanding in Multi-Modal La...
PDF
論文紹介:What, when, and where? ​Self-Supervised Spatio-Temporal Grounding​in Unt...
PDF
論文紹介:PitcherNet: Powering the Moneyball Evolution in Baseball Video Analytics
PDF
論文紹介:"Visual Genome:Connecting Language and Vision​Using Crowdsourced Dense I...
PDF
論文紹介:"InfLoRA: Interference-Free Low-Rank Adaptation for Continual Learning" ...
PDF
論文紹介:ActionSwitch: Class-agnostic Detection of Simultaneous Actions in Stream...
PDF
論文紹介:Make Pixels Dance: High-Dynamic Video Generation
PDF
PCSJ-IMPS2024招待講演「動作認識と動画像符号化」2024年度画像符号化シンポジウム(PCSJ 2024) 2024年度映像メディア処理シンポジ...
PDF
論文紹介:T-DEED: Temporal-Discriminability Enhancer Encoder-Decoder for Precise E...
PDF
論文紹介:On Feature Normalization and Data Augmentation
PDF
論文紹介:CLIFF: Continual Latent Diffusion for Open-Vocabulary Object Detection
PDF
論文紹介:MS-DETR: Efficient DETR Training with Mixed Supervision
PDF
論文紹介:Synergy of Sight and Semantics: Visual Intention Understanding with CLIP
論文紹介:Unboxed: Geometrically and Temporally Consistent Video Outpainting
論文紹介:OVO-Bench: How Far is Your Video-LLMs from Real-World Online Video​ Unde...
論文紹介:HOTR: End-to-End Human-Object Interaction Detection​ With Transformers, ...
論文紹介:Segment Anything, SAM2: Segment Anything in Images and Videos
論文紹介:Unbiasing through Textual Descriptions: Mitigating Representation Bias i...
論文紹介:AutoPrompt: Eliciting Knowledge from Language Models with Automatically ...
論文紹介:「Amodal Completion via Progressive Mixed Context Diffusion」「Amodal Insta...
論文紹介:「mPLUG-Owl3: Towards Long Image-Sequence Understanding in Multi-Modal La...
論文紹介:What, when, and where? ​Self-Supervised Spatio-Temporal Grounding​in Unt...
論文紹介:PitcherNet: Powering the Moneyball Evolution in Baseball Video Analytics
論文紹介:"Visual Genome:Connecting Language and Vision​Using Crowdsourced Dense I...
論文紹介:"InfLoRA: Interference-Free Low-Rank Adaptation for Continual Learning" ...
論文紹介:ActionSwitch: Class-agnostic Detection of Simultaneous Actions in Stream...
論文紹介:Make Pixels Dance: High-Dynamic Video Generation
PCSJ-IMPS2024招待講演「動作認識と動画像符号化」2024年度画像符号化シンポジウム(PCSJ 2024) 2024年度映像メディア処理シンポジ...
論文紹介:T-DEED: Temporal-Discriminability Enhancer Encoder-Decoder for Precise E...
論文紹介:On Feature Normalization and Data Augmentation
論文紹介:CLIFF: Continual Latent Diffusion for Open-Vocabulary Object Detection
論文紹介:MS-DETR: Efficient DETR Training with Mixed Supervision
論文紹介:Synergy of Sight and Semantics: Visual Intention Understanding with CLIP

ソフトウェア工学2023 11 テスト