SlideShare a Scribd company logo
継続的にテスト可能な設計を考える
ContinuousTestable Design
Atsushi Nakamura
About Me
Copyright 2017 @nuits_jp Slide 2
中村 充志 / Atsushi Nakamura
• リコージャパン株式会社 金融事業部所属
• Enterprise系SIerのITアーキテクト
• JavaからC#へ渡り歩く
• 趣味はXamarin?
• Blog http://www.nuits.jp
• Blog(英語) https://blog.nuits.jp
• Twitter @nuits_jp
テスト書いてますか?
Copyright 2017 @nuits_jp Slide 3
テストを維持し続けるのは難しい!
Copyright 2017 @nuits_jp Slide 4
ContinuousTestable Design
Today’s Goal
Today’s Goal
Slide 6Copyright 2017 @nuits_jp
継続的にテスト可能な設計を実現するには多くのエッセンスが必要です。
今日はそのうち、つぎの3つについてお話します。
1. 制御の流れと依存方向の分離
2. 依存方向の制御と、安定性と柔軟性の管理
3. 現実的なテスト戦略を考える(結論はこの場ではでない)
「継続的なテストの維持」に必要な一部のエッセンスですが、非常に重要なことです。
ContinuousTestable Design
Overview
1. テスト不可のアプリからスタート
2. リファクタリングしつつ、継続的にテスト可能な設計を目指す
3. テストの対象は「今回は」クラス単位を想定
Overview
• 対象システムは次のような特徴をもちます
• プロダクト別の総売上をCSV出力するコンソール アプリを想定
• 同一のデータベースを他の機能からも利用している
Overview
Copyright 2017 @nuits_jp Slide 9
SQL Server 2017
AdventureWorks2017
Other Functions
※英語含む、対象コードへのマサカリは「そっと」
Pull Requestを投げるというソフト対応をお願いします
https://github.com/nuitsjp/Continuous-Testable-Design
ContinuousTestable Design
プロダクト別 総売上 出力システム
まずはコードを見てみよう
Copyright 2017 @nuits_jp Slide 11
現在の構造
クラス間が直接依存しており
上流のクラスの単体テストができない
ControllerとBusinessLogicの関係
現在、ControllerとBusinessLogicの間には二つの依存関係がある
•BusinessLogicの生成
•BusinessLogicの利用
Controllerをテスト ダブル(Mock・Stub・Fakeなど)を利用してテストできるよ
うにリファクタリングする。
Copyright 2017 @nuits_jp Slide 13
インターフェースの抽出
Copyright 2017 @nuits_jp Slide 14
インターフェースの抽出結果
Copyright 2017 @nuits_jp Slide 15
○ 利用箇所はインターフェース依存になった
× 生成箇所に実装クラス依存が残っている
No. 方式 代表的なデザインパターン
1 Controllerが能動的に取得する Service Locator パターン
2 Controllerに外部から注入する Dependency Injection パターン
インスタンス生成を取り除く二つの方式
Copyright 2017 @nuits_jp Slide 16
基本的にいずれかに類似した方式をとります。
ここではDependency Injectionパターンを利用します。
Service Locator is an Anti-Pattern by Mark Seemann
http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/
• Service Locatorはstaticなレジストリなので並行テストが困難
• ControllerからBusinessLogicへ依存がなくなる代わりに、Service Locatorへ依存が増える
Business Logicをインジェクションする
Copyright 2017 @nuits_jp Slide 17
Dependency Injectionの適用結果
Copyright 2017 @nuits_jp Slide 18
IBusinessLogic businessLogic =
new BusinessLogic();
var controller =
new Controller(businessLogic);
controller.Execute("output.csv");
テスト ダブルの利用
Copyright 2017 @nuits_jp Slide 19
IBusinessLogic businessLogic =
newTestDouble();
var controller =
new Controller(businessLogic);
controller.Execute("output.csv");
さあ、すべてテスタブルに修正しましょう!
Copyright 2017 @nuits_jp Slide 20
しました!
Copyright 2017 @nuits_jp Slide 21
結果、こんな感じでテストできます
Copyright 2017 @nuits_jp Slide 22
単体テスト用DB
class Testable Models
Repositoryの単体テスト
BusinessLogicの単体テスト
Controllerの単体テスト
Controller «interface»
IBusinessLogic
BusinessLogic «interface»
IRepository
Repository
ControllerFixture
BusinessLogicMock
BusinessLogicFixture
RepositoryFixture
RepositoryMock
ところで、よく見ると
Copyright 2017 @nuits_jp Slide 23
なんか嫌な臭いがするぞ?
Copyright 2017 @nuits_jp Slide 24
特にこのあたりが...
Copyright 2017 @nuits_jp Slide 25
ここの向き
良く見てみよう
ControllerがViewに、依存している
一般的にViewは最も変化が多い
⇒ ControllerはViewに引きずられ変更過多になる
⇒ 結果、Controllerのテストコードもテストダブルも変更過多になる
⇒つらい
Copyright 2017 @nuits_jp Slide 26
もうひとつ
Copyright 2017 @nuits_jp Slide 27
ここもちょっとな...
Copyright 2017 @nuits_jp Slide 28
ここの向き
• 同一のデータベースを他の機能からも利用している
• データベースは他システム起因で変更が入る
• そもそもデータが安定的だというのは過去の神話で
ビジネスの変遷の早い現在は、データも安定的ではなくなっている
あらためて全体像を確認する
Copyright 2017 @nuits_jp Slide 29
SQL Server 2017
AdventureWorks2017
Other Functions
class Testable Models
Repositoryの単体テスト
BusinessLogicの単体テスト
Controllerの単体テスト
Controller «interface»
IBusinessLogic
BusinessLogic «interface»
IRepository
Repository
ControllerFixture
BusinessLogicMock
BusinessLogicFixture
RepositoryFixture
RepositoryMock
つまり参照してるテーブルが変更されると…
Copyright 2017 @nuits_jp Slide 30
単体テスト用DB
Breaking
Change!
Breaking
Change!
Breaking
Change!
Breaking
Change!
Breaking
Change!
Breaking
Change!
Breaking
Change!
Breaking
Change!
Breaking
Change!
Breaking
Change!
Breaking
Change!
最悪だ!!
Copyright 2017 @nuits_jp Slide 31
何が悪いのか?
Copyright 2017 @nuits_jp Slide 32
class BusinessLogic and Repository
BusinessLogics Repositories
BusinessLogic «interface»
IRepository
Repository
制御の流れに
引きずられて
安定させたい
モジュール
不安定な
モジュール
依存して
しまっている
解決策
Copyright 2017 @nuits_jp Slide 33
class BusinessLogic and Repository
BusinessLogics Repositories
BusinessLogic «interface»
IRepository
Repository
制御の流れから
依存方向を分離し
逆方向へ依存させる
できます!
Copyright 2017 @nuits_jp Slide 34
こうする!
重要なのは、インターフェースの移動ではなく
IRepositoryをBusinessLogicの文脈で定義すること
Copyright 2017 @nuits_jp Slide 35
class BusinessLogic and Repository
BusinessLogics Repositories
BusinessLogic «interface»
IRepository
Repository
class BusinessLogic and Repository
BusinessLogics Repositories
BusinessLogic «interface»
IRepository
Repository
Before
After
IRepositoryの詳細を見てみましょう
Copyright 2017 @nuits_jp Slide 36
IRepositoryのクラス図とER図
Copyright 2017 @nuits_jp Slide 37
class Repository Details
«interface»
IRepository
+ GetProducts(): IEnumerable<Product>
+ GetSalesOrderDetail(): IEnumerable<SalesOrderDetail>
SalesOrderDetail
«property»
+ CarrierTrackingNumber(): string
+ LineTotal(): decimal
+ ModifiedDate(): DateTime
+ OrderQty(): short
+ ProductID(): int
+ rowguid(): Guid
+ SalesOrderDetailID(): int
+ SalesOrderID(): int
+ SpecialOfferID(): int
+ UnitPrice(): decimal
+ UnitPriceDiscount(): decimal
Product
«property»
+ Class(): string
+ Color(): string
+ DaysToManufacture(): int
+ DiscontinuedDate(): DateTime?
+ FinishedGoodsFlag(): bool
+ ListPrice(): decimal
+ MakeFlag(): bool
+ ModifiedDate(): DateTime
+ Name(): string
+ ProductID(): int
+ ProductLine(): string
+ ProductModelID(): int?
+ ProductNumber(): string
+ ProductSubcategoryID(): int?
+ ReorderPoint(): short
+ rowguid(): Guid
+ SafetyStockLevel(): short
+ SellEndDate(): DateTime?
+ SellStartDate(): DateTime
+ Size(): string
+ SizeUnitMeasureCode(): string
+ StandardCost(): decimal
+ Style(): string
+ Weight(): decimal?
+ WeightUnitMeasureCode(): string
dm SalesOrderDetail and Product
Product
«column»
*PK ProductID: int
* Name: nvarchar(50)
* ProductNumber: nvarchar(25)
* MakeFlag: bit = 1
* FinishedGoodsFlag: bit = 1
Color: nvarchar(15)
* SafetyStockLevel: smallint
* ReorderPoint: smallint
* StandardCost: money
* ListPrice: money
Size: nvarchar(5)
FK SizeUnitMeasureCode: nchar(3)
FK WeightUnitMeasureCode: nchar(3)
Weight: decimal(8,2)
* DaysToManufacture: int
ProductLine: nchar(2)
Class: nchar(2)
Style: nchar(2)
FK ProductSubcategoryID: int
FK ProductModelID: int
* SellStartDate: datetime
SellEndDate: datetime
DiscontinuedDate: datetime
* rowguid: uniqueidentifier = newid()
* ModifiedDate: datetime = getdate()
SalesOrderDetail
«column»
*pfK SalesOrderID: int
*PK SalesOrderDetailID: int
CarrierTrackingNumber: nvarchar(25)
* OrderQty: smallint
*FK ProductID: int
*FK SpecialOfferID: int
* UnitPrice: money
* UnitPriceDiscount: money = 0.0
* LineTotal: numeric(38,6)
* rowguid: uniqueidentifier = newid()
* ModifiedDate: datetime = getdate()
クラス図 ER図
IRepositoryが完全にデータベースの文脈で記述されているのが見て取れる
BusinessLogicのインターフェースと比較する
Copyright 2017 @nuits_jp Slide 38
dm BusinessLogic
«interface»
IBusinessLogic
+ GetProductSalesList(): IEnumerable<ProductSales>
ProductSales
«property»
+ Name(): string
+ Sales(): decimal
プロダクト別の総売上額が欲しいだけ
class Repository Details
«interface»
IRepository
+ GetProducts(): IEnumerable<Product>
+ GetSalesOrderDetail(): IEnumerable<SalesOrderDetail>
SalesOrderDetail
«property»
+ CarrierTrackingNumber(): string
+ LineTotal(): decimal
+ ModifiedDate(): DateTime
+ OrderQty(): short
+ ProductID(): int
+ rowguid(): Guid
+ SalesOrderDetailID(): int
+ SalesOrderID(): int
+ SpecialOfferID(): int
+ UnitPrice(): decimal
+ UnitPriceDiscount(): decimal
Product
«property»
+ Class(): string
+ Color(): string
+ DaysToManufacture(): int
+ DiscontinuedDate(): DateTime?
+ FinishedGoodsFlag(): bool
+ ListPrice(): decimal
+ MakeFlag(): bool
+ ModifiedDate(): DateTime
+ Name(): string
+ ProductID(): int
+ ProductLine(): string
+ ProductModelID(): int?
+ ProductNumber(): string
+ ProductSubcategoryID(): int?
+ ReorderPoint(): short
+ rowguid(): Guid
+ SafetyStockLevel(): short
+ SellEndDate(): DateTime?
+ SellStartDate(): DateTime
+ Size(): string
+ SizeUnitMeasureCode(): string
+ StandardCost(): decimal
+ Style(): string
+ Weight(): decimal?
+ WeightUnitMeasureCode(): string
IRepositoryをBusinessLogicの文脈へリファクタリング
Copyright 2017 @nuits_jp Slide 39
リファクタリング後のIRepository
Copyright 2017 @nuits_jp Slide 40
dm Repositories
ProductName
«property»
+ Name(): string
+ ProductId(): int?
SalesLineTotal
«property»
+ LineTotal(): double
+ ProductId(): int
«interface»
IRepository
+ GetProductNames(): IEnumerable<ProductName>
+ GetSalesLineTotal(): IEnumerable<SalesLineTotal>
一旦整理しましょう
Copyright 2017 @nuits_jp Slide 41
「制御の流れ」と「依存方向」は、つぎの二つによりコントロール可能
• クラスとクラスは直接依存させず、インターフェース(抽象)に依
存させる(依存性逆転の原則)
• インターフェースを適切な文脈で定義する
「制御の流れ」と「依存方向」
Copyright 2017 @nuits_jp Slide 42
class BusinessLogics and Re...
Repositories
BusinessLogics
BusinessLogic
«interface»
IRepository
Repository
依存方向によって決まる、安定性と柔軟性
Copyright 2017 @nuits_jp Slide 43
Repository変更の影響を受けない ⇒ 安定性が高い
変更がRepositoryへ影響を与える ⇒ 柔軟性が低い
BusinessLogic変更の影響を受ける ⇒ 安定性が低い
変更がBusinessLogicへ影響を与えない ⇒ 柔軟性が高い
安定性と柔軟性は設計上トレードオフにある
あらためて全体を見てみる
Copyright 2017 @nuits_jp Slide 44
元々の安定度と柔軟性
Copyright 2017 @nuits_jp Slide 45
凡例
制御の流れ
依存関係
安定性③
柔軟性①
安定性①
柔軟性②
安定性②
柔軟性②
安定性①
柔軟性③
安定性①
柔軟性②
安定性①
柔軟性③
安定性と柔軟性 パッケージ
安定度:高 BusinessLogic
中間 Controller
柔軟性:高 View, Repository
安定性と柔軟性をコントロールする
Copyright 2017 @nuits_jp Slide 46
安定度と柔軟性をコントロールする
Copyright 2017 @nuits_jp Slide 47
凡例
制御の流れ
依存関係
安定性②
柔軟性②
安定性③
柔軟性①
安定性①
柔軟性③
安定性②
柔軟性①
継続的にテスト可能になった(はず)のクラス図
すべてのクラスを同じレベルでテストしますか?
Copyright 2017 @nuits_jp Slide 49
安定性(重要性)とテストの寿命
Copyright 2017 @nuits_jp Slide 50
安定性(重要性) テストコードの寿命
高 長い
低 短い
テスト価値と難易度には一貫性はない
Copyright 2017 @nuits_jp Slide 51
安定性 要素 テスト価値 テスト難易度
高 BusinessLogic 高 低
中
Repository 高 中
Controller 中 低
低 View 低 高
Viewのテストしたくねえな…
Copyright 2017 @nuits_jp Slide 52
そもそもクラス単体テストの価値って?
Copyright 2017 @nuits_jp Slide 53
システム
サブシステム
コンポーネント
ソフトウェアのフラクタル
Copyright 2017 @nuits_jp Slide 54
クラス
サブシステム
クラス
コンポーネント
クラス クラス
ソフトウェア エンティティすべてに、ここまでの話は適用可能
• 要素間のインターフェースの文脈により、制御の流れと依存関係は制御可能
• 依存関係によって、安定性と柔軟性をコントロール可能
ソ
フ
ト
ウ
ェ
ア
エ
ン
テ
ィ
テ
ィ
ソフトウェア エンティティ テスト価値 テスト難易度 障害発見
システム 高 高 遅い
サブシステム
コンポーネント
クラス 低 低 早い
ソフトウェア エンティティ別のテスト価値とテスト難易度
Copyright 2017 @nuits_jp Slide 55
品質をあ
げたい
テストを
増やす
テスト維
持コスト
上昇
維持でき
なくなる
品質が下
がる
テストのジレンマ
Copyright 2017 @nuits_jp Slide 56
テストには計画的な戦略が必要だ!
Copyright 2017 @nuits_jp Slide 57
などなど
テスト戦略の立案
Copyright 2017 @nuits_jp Slide 58
プロジェクト
計画
ビジネス
ユースケース
システム
ユースケース
機能要件
非機能要件
システム
アーキテクチャ
テスト戦略
What システム サブシステム コンポーネント クラス
Why
Who
When
Where
Hoe
How many
How Much
テスト戦略の立案
Copyright 2017 @nuits_jp Slide 59
先ほどのソフトウェア エンティティに対してmWnHで立てます
What システム
Why ビジネス シナリオ(業務)の実現性評価
Who 開発サイドのテストチーム
When 評価可能になり次第随時
Where システムテスト環境(Not顧客環境)
How ビジネス シナリオに則って手動で
How many 想定されるすべてのビジネスシナリオ
How Much 全ビジネスシナリオで〇〇人月
テスト戦略:システム
Copyright 2017 @nuits_jp Slide 60
ContinuousTestable Design
まとめ
まとめ
Slide 62Copyright 2017 @nuits_jp
1. 制御の流れに流されず、適切な依存方向にコントロールする
2. 依存方向によって安定性と柔軟性を、計画的に制御しましょう
3. 多面的な要素を考慮し、現実的なテスト戦略を立てましょう
ThankYou!
Any Questions?

More Related Content

PDF
ドメイン駆動設計のための Spring の上手な使い方
PDF
イミュータブルデータモデルの極意
PDF
REST API のコツ
PPTX
世界一わかりやすいClean Architecture
PDF
開発速度が速い #とは(LayerX社内資料)
PDF
マイクロにしすぎた結果がこれだよ!
PDF
マルチテナントのアプリケーション実装〜実践編〜
PDF
Form認証で学ぶSpring Security入門
ドメイン駆動設計のための Spring の上手な使い方
イミュータブルデータモデルの極意
REST API のコツ
世界一わかりやすいClean Architecture
開発速度が速い #とは(LayerX社内資料)
マイクロにしすぎた結果がこれだよ!
マルチテナントのアプリケーション実装〜実践編〜
Form認証で学ぶSpring Security入門

What's hot (20)

PDF
ヤフー社内でやってるMySQLチューニングセミナー大公開
PDF
Kubernetes 疲れに Azure Container Apps はいかがでしょうか?(江東区合同ライトニングトーク 発表資料)
PPTX
MongoDBが遅いときの切り分け方法
PDF
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
PDF
Dockerからcontainerdへの移行
PDF
MagicOnion入門
PDF
オブジェクト指向エクササイズのススメ
PDF
Prometheus at Preferred Networks
PDF
Linux女子部 systemd徹底入門
PDF
イミュータブルデータモデル(世代編)
PDF
【BS13】チーム開発がこんなにも快適に!コーディングもデバッグも GitHub 上で。 GitHub Codespaces で叶えられるシームレスな開発
PDF
MagicOnion~C#でゲームサーバを開発しよう~
PDF
CEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭する
PDF
なぜ、いま リレーショナルモデルなのか(理論から学ぶデータベース実践入門読書会スペシャル)
PDF
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
PDF
Apache Airflow入門 (マーケティングデータ分析基盤技術勉強会)
PDF
Pull Request & TDD 入門
PDF
マイクロサービス 4つの分割アプローチ
PDF
大規模ソーシャルゲームを支える技術~PHP+MySQLを使った高負荷対策~
PDF
新入社員のための大規模ゲーム開発入門 サーバサイド編
ヤフー社内でやってるMySQLチューニングセミナー大公開
Kubernetes 疲れに Azure Container Apps はいかがでしょうか?(江東区合同ライトニングトーク 発表資料)
MongoDBが遅いときの切り分け方法
SQLアンチパターン 幻の第26章「とりあえず削除フラグ」
Dockerからcontainerdへの移行
MagicOnion入門
オブジェクト指向エクササイズのススメ
Prometheus at Preferred Networks
Linux女子部 systemd徹底入門
イミュータブルデータモデル(世代編)
【BS13】チーム開発がこんなにも快適に!コーディングもデバッグも GitHub 上で。 GitHub Codespaces で叶えられるシームレスな開発
MagicOnion~C#でゲームサーバを開発しよう~
CEDEC 2018 最速のC#の書き方 - C#大統一理論へ向けて性能的課題を払拭する
なぜ、いま リレーショナルモデルなのか(理論から学ぶデータベース実践入門読書会スペシャル)
SQLアンチパターン - 開発者を待ち受ける25の落とし穴 (拡大版)
Apache Airflow入門 (マーケティングデータ分析基盤技術勉強会)
Pull Request & TDD 入門
マイクロサービス 4つの分割アプローチ
大規模ソーシャルゲームを支える技術~PHP+MySQLを使った高負荷対策~
新入社員のための大規模ゲーム開発入門 サーバサイド編
Ad

Similar to 継続的にテスト可能な設計を考える (20)

PPTX
α版 継続的にテスト可能な設計を考える
PPTX
継続的にテスト可能な設計を考える ベータ版
PDF
RxDataSourceをNSDiffableDataSourceへ置き換える際のTips集紹介
PDF
お客様が望んでいるモダンデスクトップアプリとは?/傾向と対策 Part1
PDF
[Japan Tech summit 2017] DAL 006
PDF
Ms retail update ra 20191030
PPTX
「関心の分離」と「疎結合」 ソフトウェアアーキテクチャのひとかけら
PDF
ファーストアカウンティング会社説明資料 for engineer 2022年7月版
PPTX
そのデータ、活かせていますか?
PPTX
繋ぐだけじゃ終わらない! IoTを手軽にビジネスプロセスへ統合する Azure IoT + Dynamics 365 の紹介
PDF
システムのモダナイズ 落ちても良いアプリの作り方
PDF
Angularreflex20141210
PDF
Adobe XD Plugin「Scenegraph」の操作とDialogの作り方
PDF
JavaOne2017参加報告 Microservices topic & approach #jjug
PDF
20190515 hccjp hybrid_strategy
PDF
CodeIgniter 〜 2008年大躍進のPHPフレームワーク
PDF
QualityとDeliveryを両立させるために僕らがやったこと
PPTX
OCHaCafe Season 2 #4 - Cloud Native時代のモダンJavaの世界
PDF
iOSやAndroidアプリ開発のGoodPractice
PDF
ヒーロー島 Visual Studio 2012
α版 継続的にテスト可能な設計を考える
継続的にテスト可能な設計を考える ベータ版
RxDataSourceをNSDiffableDataSourceへ置き換える際のTips集紹介
お客様が望んでいるモダンデスクトップアプリとは?/傾向と対策 Part1
[Japan Tech summit 2017] DAL 006
Ms retail update ra 20191030
「関心の分離」と「疎結合」 ソフトウェアアーキテクチャのひとかけら
ファーストアカウンティング会社説明資料 for engineer 2022年7月版
そのデータ、活かせていますか?
繋ぐだけじゃ終わらない! IoTを手軽にビジネスプロセスへ統合する Azure IoT + Dynamics 365 の紹介
システムのモダナイズ 落ちても良いアプリの作り方
Angularreflex20141210
Adobe XD Plugin「Scenegraph」の操作とDialogの作り方
JavaOne2017参加報告 Microservices topic & approach #jjug
20190515 hccjp hybrid_strategy
CodeIgniter 〜 2008年大躍進のPHPフレームワーク
QualityとDeliveryを両立させるために僕らがやったこと
OCHaCafe Season 2 #4 - Cloud Native時代のモダンJavaの世界
iOSやAndroidアプリ開発のGoodPractice
ヒーロー島 Visual Studio 2012
Ad

More from Atsushi Nakamura (16)

PPSX
Settings SyncとCodespaceで体験する新世代へのパラダイムシフト
PPTX
C#メタプログラミング概略 in 2021
PPTX
Unicodeで半角全角を扱うAmbiguous(曖昧さ)とUncertainty(不確実性)の恐怖
PPTX
世界一わかりやすいClean Architecture - DroidKaigiバージョン
PPTX
世界一わかりやすいClean Architecture release-preview
PPTX
世界一わかりやすいClean Architecture alpha-1
PPTX
Visual Studio 2019で始める「WPF on .NET Core 3.0」開発
PPTX
Desktop app dev strategy for .net core 3.0
PDF
App center analyticsを使い倒そう
PPTX
Old:App center analyticsを使い倒そう
PPTX
Xamarin.forms navigation overview
PPTX
App center analyticsを使い倒そう
PPTX
Blue monkey architecture overview
PPTX
Xamarin Dev days 2 xamarin.forms ja
PPTX
Why prism for xamarin.forms
PPTX
Enterpriseから見たXamarinの可能性
Settings SyncとCodespaceで体験する新世代へのパラダイムシフト
C#メタプログラミング概略 in 2021
Unicodeで半角全角を扱うAmbiguous(曖昧さ)とUncertainty(不確実性)の恐怖
世界一わかりやすいClean Architecture - DroidKaigiバージョン
世界一わかりやすいClean Architecture release-preview
世界一わかりやすいClean Architecture alpha-1
Visual Studio 2019で始める「WPF on .NET Core 3.0」開発
Desktop app dev strategy for .net core 3.0
App center analyticsを使い倒そう
Old:App center analyticsを使い倒そう
Xamarin.forms navigation overview
App center analyticsを使い倒そう
Blue monkey architecture overview
Xamarin Dev days 2 xamarin.forms ja
Why prism for xamarin.forms
Enterpriseから見たXamarinの可能性

継続的にテスト可能な設計を考える

Editor's Notes

  • #2: みなさんこんにちは。 ご紹介いただきました。ニュイこと中村です。 今日は 継続的にテスト可能な設計を考える というTitleでお話しさせていただこうと思います。 よろしくお願いいたします。
  • #3: まずは自己紹介から 中村充志と申します。 リコージャパン株式会社の金融ソリューション開発部 というところに所属しています。 Enterprise系SIerでITアーキテクトをやらせてもらっています。
  • #4: ところで皆さん、日常的に自動テスト書いてますか? 私は結構書いてます。 SIerのテストというと、Excel!スクショ!エビデンス!的なイメージがあるかも知れませんが たまたま巡り合わせが良くて、2000年からJUnitを触っていたと思います。 SIerって、プロダクトチームの維持が難しくて、半年ごとに8割のメンバーが入れ替わってる みたいな地獄みたいな事が良くあるので、自動テストが無いと怖くて生きていけない というのが、私のテストのモチベーションなんだと思います。 ただ、テストを書くのは良いんですが、本当に難しいのはプロダクトの改修に追随して テストを維持していくことだと、私は思っています。
  • #5: テストを維持し続けるのは本当に難しいです。 プロジェクトのスケジュールが厳しくなると、すぐ悪魔が囁き始めますし テストを維持する事自体が、ソフトウェアの変更を阻害するようなこともあります。 実際に、テストをいくらか破棄して泣く泣く規模を縮小したりしたこともあります。
  • #6: という訳で、本日のゴールです。
  • #7: 今日は、そういった茨の道で試行錯誤してきたベストプラクティスの中から 特に大切だと思っている、三つのエッセンスについてお話ししたいと思います。 ①一つは制御の流れと依存性の分離について ②二つ目は依存方向の制御と、安定性と柔軟性の管理について ③そして最後に現実的なテスト戦略について です。当然これだけで継続的なテストの維持ができるようになる訳ではありませんが、特に重要なことだと私は思っていますのでお付き合いください。
  • #8: このセッションの概要ですが
  • #9: 実際のコードを見ながら、最初はテスト不可能なコードからスタートして リファクタリングしながら、継続的にテスト可能な設計を目指したいと思います。 なお、テストの対象は今回はクラス単位のテストをする前提とさせていただきます。
  • #10: 実際の例に用いるプログラムですが、SQL ServerのサンプルDBのAdventureWorksを 利用させてもらって、プロダクト別の総売上をCSV出力するような コンソールアプリを想定します。 そのアプリから利用するデータベースは、同一システムの別機能からも利用されるものとします。 コードはこちらのURLに公開しています。 なおコードや、コードの英語に不備不満のある方は、そっとプルリクを送っていただけると助かります。 まえに「その単語、不可算名詞だからsつくのおかしくね?」みたいなマサカリをぶん投げてきた人がいるんですが なるべく、そっとやさしくお願いします。 今日は強そうな人が多いので、重々よろしくお願いいたします。
  • #11: では早速、本題に入りたいと思います。
  • #12: まずはコードを見てください。
  • #13: ① さて、今見ていただいたコードですが クラス図に落とすと、こんな感じです。 今日のテスト対象はクラスですから ② この辺のクラス間が直接依存してるせいで 上流のクラスが単体テストできない状態になっています。
  • #14: 例えばControllerとBusinessLogicの関係の詳細を見てみると その二つの間には、オブジェクトの生成と利用という2種類の依存関係があります。 これらが、Controllerをテストダブルを利用して単体テストすることを阻んでいます。 という訳で、これらのクラス間の直接的な依存関係を取り除いていきます。
  • #15: まずはインターフェースを抽出します。 ここからは、またコードに戻ります。
  • #16: さて、こうしたことで、BusinessLogicを利用している箇所の依存関係は クラスからインターフェース移すことができました。 ただし、インスタンスを生成する個所に依存がまだ残っています。
  • #17: インスタンス生成の依存関係を取り除くには、基本的にはインスタンスの生成を つぎの二つの何れかの方法で解決する必要があります。 一つはControllerが能動的に、インスタンスをいずれかのレジストリーに取得しに行く方法。 もう一つは依存オブジェクトをControllerの外から、誰かに注入してもらう方法です。 それぞれ代表的な実現方法として、ServiceLocatorパターンとDependency Injectionパターンがあります。 今回はDependency Injectionパターン、つまりDIを利用します。 なぜDIを利用するかは、ざっくりいうと、ServiceLocatorには大きな問題が二つあって 一つはテストをマルチスレッドで実装しにくいということと ServiceLocatorパターンにするとBusinessLogicへの依存は減るけど、ServiceLocator への依存が増えて、依存数が減らないという欠点があるためです。
  • #18: という訳でBusinessLogicをインジェクションするように修正します。
  • #19: というわけで、無事にControllerからインスタンス生成のロジックも除去出来て 完全にクラス間の依存関係を無くせました。
  • #20: こうする事で、簡単にBusinessLogicをテストダブルに置き換えが可能になって テスタブルになります。 ちなみにDIパターンとDIコンテナが混同されているケースがありますが DIコンテナはあくまでDIパターンを便利に実現するためのツールなので 必ずしも使わなくてもDIパターンは実現できます。普通は使いますけど。
  • #21: では他の部分もテスタブルにしましょう!
  • #22: はいできました!すいません、時間がないので3分間クッキング方式を 取らせていただきます。 View、BusinessLogic、Repositoryの全てに インターフェースを導出し、DIを適用します。 こうする事で
  • #23: こんな感じで、全てのクラスにたいしてクラス単位のテストが実施できるようになりました。
  • #24: ところでこのクラス図
  • #25: 良く見ると不吉なにおいがしますね?
  • #26: 特にこの、ControllerとViewの間です。
  • #27: ①ControllerがViewに依存している部分です。 ②一般的にViewは最も変化が多いと言われていますよね。 それが正しいとすると ③Viewに依存しているControllerはViewに引きずられて 頻繁に変更する必要がでてきます ④結果、プロダクションコードだけでなく、テストコードもテストダブルも 頻繁に変更しなくてはならなくなります ⑤これは辛いです
  • #28: そしてもう一つ 個人的にはこっちの方が嫌な予感がします。
  • #29: このBusinessLogicがRepositoryに依存しているところです。
  • #30: 最初にお話しした通り、このシステムで利用するデータベースは 他の機能からも利用されます。 ということは、このシステムではない、別の機能起因で変更が入る可能性があります。 そもそもデータが安定的であるというのも、私は懐疑的です。 現代において企業は、顧客に対して新しい価値を次々提供し続けなくてはなりません。 新しい価値を提供するためには、往々にして新しいデータが発生します。 データの取り扱いが無くなる事は少なくても、追加変更はそれなりに発生するのが現実だと 私は思っています。
  • #31: ①つまり、何らかの要因で参照しているテーブルに変更が発生すると ②リポジトリが変更されます ③当然そうなるとテストケースの修正が入って ④高い確率でリポジトリのインターフェースが変更されます ⑤するとビジネスロジックが影響を受けて ⑥リポジトリのモックとビジネスロジックのテストががががが 全部なおさなきゃ!
  • #32: 最悪だ!と、なってしまうかもしれないです。 実際稀に良くありますし。
  • #33: つまり何が悪いかというと ①安定させたいビジネスロジックが ②不安定なモジュールにたいして ③制御の流れが原因で引きずられて ④依存してしまっているのが、この問題の根幹にあります これを解決するには
  • #34: 制御の流れから依存方向を分離して ①逆方向に依存させれば解決できます
  • #35: もちろん実現可能です
  • #36: 具体的には、今こうなっているのを ①こうします。重要なのは、インターフェースを移動することではなくて ②リポジトリをビジネスロジックの文脈で定義することです
  • #37: リポジトリの詳細を見てみましょう
  • #38: 現在の実装は、リポジトリが完全にデータベースの文脈で記述されているのが見て取れます。
  • #39: しかし、ビジネスロジックで実現したいのは、プロダクト別の総売上額です。 ①つまりビジネスロジックに必要なのはこの4項目だけです。
  • #40: という訳で、リポジトリインターフェースを ビジネスロジックの文脈へリファクタリングします
  • #41: 結果、こうなりました。 データベースの詳細が隠蔽されているのが 分かるかと思います。
  • #42: 一旦整理しましょう。
  • #43: 制御の流れと、依存方向は、必ずしも一致させる必要はありません。 クラスとクラスの直接依存を避けて、インターフェースを定義し、疎結合にした上で インターフェースを依存させたい側の文脈で定義することで 依存方向は制御の流れから分離して、自由にコントロールできます。
  • #44: そして、依存方向によって安定性と柔軟性が変化します。 リポジトリインターフェースは、ビジネスロジックの文脈で記述されています。 ①つまり、データベースの影響を受けにくく、安定性が高くなります。 しかし逆にいうと、ビジネスロジックを変更すると、リポジトリの実装が影響を受ける可能性があるため ビジネスロジックの柔軟性は低くなります。 ②逆にリポジトリの実装はビジネスロジックの変更の影響を受ける為、安定性は低くなります。 しかし、リポジトリの変更はビジネスロジックへ影響を与えにくいため、変更しやすく、柔軟性が高いといえます。 ③つまり、安定性と柔軟性は設計上のトレードオフにあるわけです。
  • #45: あらためて全体を見てみましょう。
  • #46: クラスを書くと煩雑なので、パッケージだけ記述しました。 最初の設計では制御の流れと依存関係が一致していたため それぞれのパッケージの安定性と柔軟性は、この図のような 状態にあります。 ①ビューとリポジトリが一番安定している。つまり変更しにくい状態にあり それらを修正すると、システム全体への影響が大きい状態に なってしまっています。
  • #47: 実際には安定性と柔軟性は、だいたいこんな感じにしたいとします。 ビジネスロジックを最も安定させ ビューとレポジトリーは柔軟性を高めたい、つまり変更しやすくしたいとします。
  • #48: その為にはコントローラーとビュー、ビジネスロジックとリポジトリの間の 依存関係を逆転してあげれば、望む通りの安定性と柔軟性を 得ることができるわけです。
  • #49: これで変更を受けやすい部分の柔軟性が高くなり、それ以外への影響が およびにくくなりました。 つまり、テストコードへの影響も局所化できて、継続的にテストをしやすい状態に なったはずです。 なったんですが、そこで疑問が発生します。
  • #50: 全部テストできるからと、すべてのクラスを同じレベルでテストする必要があるんでしょうか?
  • #51: 当たり前のことですが、重要なクラスの安定性が高くなるようにコントロールすることで 重要なクラスへのテストコードの寿命は延びます。 逆に言うと、安定性の低いクラスのテストコードの寿命は短くなります。
  • #52: また、この表はあくまで例ですが、テスト価値と、テストの難易度には一貫性がないのではないかと感じています。 重要な部分はテストしにくい部分から切り離す努力はしますが、かならずしもできるとは限りません。 例えばViewなんかは安定性が低いわりに、テスト難易度が高かったりして、きっと皆さん思いますよね?
  • #53: Viewのテストしたくねえなあって。 また別の視点もあります
  • #54: そもそも、クラス単体でテストする価値ってどのくらいあるんでしょうか?
  • #55: ①ソフトウェアはクラスとクラスがまとまってコンポーネントとなり ②コンポーネントとコンポーネントがまとまってサブシステムになります。そして ③サブシステムとサブシステムがまとまってシステムになります。 ④ここではクラス・コンポーネント・サブシステム・システムを、ソフトウェア エンティティと呼ぶことにします。 ⑤ソフトウェアエンティティのすべてにおいて、ここまでした話の内容は適用できます。 もちろんサブシステム間は例えばWeb APIになったりするでしょうけど どの要素間であっても、インターフェースの文脈を管理する事で 制御の流れと依存関係は制御することが可能です。 そして依存関係を制御することで、安定性と柔軟性をコントロールすることが可能となります
  • #56: そしてソフトウェア エンティティのレベルが高くなれば高くなるほど それらに対するテストの価値は高まります。 ただし、難易度も併せて高くなります。 クラスが動いても、システムが動かなければ価値を生まないので当たり前ですよね。 ただし、テストの比率を上位レイヤーに置くと、障害の発見が遅れます。 さらにここでジレンマが発生します。
  • #57: ①ソフトウェアの品質を上げたいという要求があったら ②通常、テストを増やして品質をあげますよね。ところがテストを増やすと ③当然ながら、テスト維持コストが上昇します。その結果 ④テストが維持できなくなります。その結果 ⑤品質が下がり そして最初にもどるという。 つまり継続的にテストをするには、テスト技術だけじゃどうにもなんないわけです。
  • #58: 我々には計画的なテスト戦略が絶対に必要です。 ではテスト戦略ってどうたてればいいのか。 ここからは完全にオレオレ方式というか、弊社方式オレ編みたいなもんですが
  • #59: テスト戦略の立案には ①プロジェクト計画や②ビジネスユースケースだったり③システムユースケース④機能要件や⑤非機能要件⑥システムアーキテクチャ ⑦などなどをインプットにして立案していきます
  • #60: で、どんな内容を計画していくかというと インプットになった情報をもとに、どの要件をどのレイヤーで、どうテストするか この表みたいな感じで例えばここでは5W3Hなどで整理していくと うまく行くことが多いです。
  • #61: もうちょっと掘り下げてみてみましょう。 例えばシステムテストであれば、こんな感じです。 システムテストは、ビジネスシナリオの実現性を評価します。 評価は開発サイドのテストチームが、評価可能になったビジネスシナリオから逐次テストします。 システムテスト用の環境でビジネスシナリオに則って手動でテストし、 想定されうるビジネスシナリオすべてに対して行います。 全ビジネスシナリオで〇〇人月投入します。 みたいな感じです。 もちろん、これは方針レベルでしかないものですが、ここから初めて詳細に落としていくやり方をとっています。
  • #62: とうわけで、最後のテスト戦略に関してはふわっとした物になってしまって申しえ訳ありませんが、私からお伝えしたいことは以上です。 最後に改めてまとめたいと思います。
  • #63: 継続的にテスト可能な設計を目指すには、次の三つのエッセンスが重要だと、私は考えています。 ①制御の流れ流されず、適切に依存方向をコントロールする必要があります ②依存方向によって安定性と柔軟性を、計画的に制御しましょう ③そして、そういった技術論だけでは継続的なテストの維持は困難なので 多面的な要素を考慮した、現実的なテスト戦略を立てる必要があるかと思います。 テスト戦略の話は、抽象的な話に終始してしまいましたが 先に話した二つのエッセンスを
  • #64: 以上で私の発表を終わります。 ご清聴ありがとうございました。