Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

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

4,893 views

Published on

.NET Conf 2018 Tokyo, Japan にて発表の「継続的にテスト可能な設計を考える」の資料です。
ソースコードなどは以下に公開しています。
https://github.com/nuitsjp/Continuous-Testable-Design

Published in: Software
  • Be the first to comment

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

  1. 1. 継続的にテスト可能な設計を考える ContinuousTestable Design Atsushi Nakamura
  2. 2. 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
  3. 3. テスト書いてますか? Copyright 2017 @nuits_jp Slide 3
  4. 4. テストを維持し続けるのは難しい! Copyright 2017 @nuits_jp Slide 4
  5. 5. ContinuousTestable Design Today’s Goal
  6. 6. Today’s Goal Slide 6Copyright 2017 @nuits_jp 継続的にテスト可能な設計を実現するには多くのエッセンスが必要です。 今日はそのうち、つぎの3つについてお話します。 1. 制御の流れと依存方向の分離 2. 依存方向の制御と、安定性と柔軟性の管理 3. 現実的なテスト戦略を考える(結論はこの場ではでない) 「継続的なテストの維持」に必要な一部のエッセンスですが、非常に重要なことです。
  7. 7. ContinuousTestable Design Overview
  8. 8. 1. テスト不可のアプリからスタート 2. リファクタリングしつつ、継続的にテスト可能な設計を目指す 3. テストの対象は「今回は」クラス単位を想定 Overview
  9. 9. • 対象システムは次のような特徴をもちます • プロダクト別の総売上をCSV出力するコンソール アプリを想定 • 同一のデータベースを他の機能からも利用している Overview Copyright 2017 @nuits_jp Slide 9 SQL Server 2017 AdventureWorks2017 Other Functions ※英語含む、対象コードへのマサカリは「そっと」 Pull Requestを投げるというソフト対応をお願いします https://github.com/nuitsjp/Continuous-Testable-Design
  10. 10. ContinuousTestable Design プロダクト別 総売上 出力システム
  11. 11. まずはコードを見てみよう Copyright 2017 @nuits_jp Slide 11
  12. 12. 現在の構造 クラス間が直接依存しており 上流のクラスの単体テストができない
  13. 13. ControllerとBusinessLogicの関係 現在、ControllerとBusinessLogicの間には二つの依存関係がある •BusinessLogicの生成 •BusinessLogicの利用 Controllerをテスト ダブル(Mock・Stub・Fakeなど)を利用してテストできるよ うにリファクタリングする。 Copyright 2017 @nuits_jp Slide 13
  14. 14. インターフェースの抽出 Copyright 2017 @nuits_jp Slide 14
  15. 15. インターフェースの抽出結果 Copyright 2017 @nuits_jp Slide 15 ○ 利用箇所はインターフェース依存になった × 生成箇所に実装クラス依存が残っている
  16. 16. 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へ依存が増える
  17. 17. Business Logicをインジェクションする Copyright 2017 @nuits_jp Slide 17
  18. 18. Dependency Injectionの適用結果 Copyright 2017 @nuits_jp Slide 18 IBusinessLogic businessLogic = new BusinessLogic(); var controller = new Controller(businessLogic); controller.Execute("output.csv");
  19. 19. テスト ダブルの利用 Copyright 2017 @nuits_jp Slide 19 IBusinessLogic businessLogic = newTestDouble(); var controller = new Controller(businessLogic); controller.Execute("output.csv");
  20. 20. さあ、すべてテスタブルに修正しましょう! Copyright 2017 @nuits_jp Slide 20
  21. 21. しました! Copyright 2017 @nuits_jp Slide 21
  22. 22. 結果、こんな感じでテストできます 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
  23. 23. ところで、よく見ると Copyright 2017 @nuits_jp Slide 23
  24. 24. なんか嫌な臭いがするぞ? Copyright 2017 @nuits_jp Slide 24
  25. 25. 特にこのあたりが... Copyright 2017 @nuits_jp Slide 25 ここの向き
  26. 26. 良く見てみよう ControllerがViewに、依存している 一般的にViewは最も変化が多い ⇒ ControllerはViewに引きずられ変更過多になる ⇒ 結果、Controllerのテストコードもテストダブルも変更過多になる ⇒つらい Copyright 2017 @nuits_jp Slide 26
  27. 27. もうひとつ Copyright 2017 @nuits_jp Slide 27
  28. 28. ここもちょっとな... Copyright 2017 @nuits_jp Slide 28 ここの向き
  29. 29. • 同一のデータベースを他の機能からも利用している • データベースは他システム起因で変更が入る • そもそもデータが安定的だというのは過去の神話で ビジネスの変遷の早い現在は、データも安定的ではなくなっている あらためて全体像を確認する Copyright 2017 @nuits_jp Slide 29 SQL Server 2017 AdventureWorks2017 Other Functions
  30. 30. 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!
  31. 31. 最悪だ!! Copyright 2017 @nuits_jp Slide 31
  32. 32. 何が悪いのか? Copyright 2017 @nuits_jp Slide 32 class BusinessLogic and Repository BusinessLogics Repositories BusinessLogic «interface» IRepository Repository 制御の流れに 引きずられて 安定させたい モジュール 不安定な モジュール 依存して しまっている
  33. 33. 解決策 Copyright 2017 @nuits_jp Slide 33 class BusinessLogic and Repository BusinessLogics Repositories BusinessLogic «interface» IRepository Repository 制御の流れから 依存方向を分離し 逆方向へ依存させる
  34. 34. できます! Copyright 2017 @nuits_jp Slide 34
  35. 35. こうする! 重要なのは、インターフェースの移動ではなく 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
  36. 36. IRepositoryの詳細を見てみましょう Copyright 2017 @nuits_jp Slide 36
  37. 37. 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が完全にデータベースの文脈で記述されているのが見て取れる
  38. 38. 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
  39. 39. IRepositoryをBusinessLogicの文脈へリファクタリング Copyright 2017 @nuits_jp Slide 39
  40. 40. リファクタリング後の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>
  41. 41. 一旦整理しましょう Copyright 2017 @nuits_jp Slide 41
  42. 42. 「制御の流れ」と「依存方向」は、つぎの二つによりコントロール可能 • クラスとクラスは直接依存させず、インターフェース(抽象)に依 存させる(依存性逆転の原則) • インターフェースを適切な文脈で定義する 「制御の流れ」と「依存方向」 Copyright 2017 @nuits_jp Slide 42
  43. 43. class BusinessLogics and Re... Repositories BusinessLogics BusinessLogic «interface» IRepository Repository 依存方向によって決まる、安定性と柔軟性 Copyright 2017 @nuits_jp Slide 43 Repository変更の影響を受けない ⇒ 安定性が高い 変更がRepositoryへ影響を与える ⇒ 柔軟性が低い BusinessLogic変更の影響を受ける ⇒ 安定性が低い 変更がBusinessLogicへ影響を与えない ⇒ 柔軟性が高い 安定性と柔軟性は設計上トレードオフにある
  44. 44. あらためて全体を見てみる Copyright 2017 @nuits_jp Slide 44
  45. 45. 元々の安定度と柔軟性 Copyright 2017 @nuits_jp Slide 45 凡例 制御の流れ 依存関係 安定性③ 柔軟性① 安定性① 柔軟性② 安定性② 柔軟性② 安定性① 柔軟性③ 安定性① 柔軟性② 安定性① 柔軟性③
  46. 46. 安定性と柔軟性 パッケージ 安定度:高 BusinessLogic 中間 Controller 柔軟性:高 View, Repository 安定性と柔軟性をコントロールする Copyright 2017 @nuits_jp Slide 46
  47. 47. 安定度と柔軟性をコントロールする Copyright 2017 @nuits_jp Slide 47 凡例 制御の流れ 依存関係 安定性② 柔軟性② 安定性③ 柔軟性① 安定性① 柔軟性③ 安定性② 柔軟性①
  48. 48. 継続的にテスト可能になった(はず)のクラス図
  49. 49. すべてのクラスを同じレベルでテストしますか? Copyright 2017 @nuits_jp Slide 49
  50. 50. 安定性(重要性)とテストの寿命 Copyright 2017 @nuits_jp Slide 50 安定性(重要性) テストコードの寿命 高 長い 低 短い
  51. 51. テスト価値と難易度には一貫性はない Copyright 2017 @nuits_jp Slide 51 安定性 要素 テスト価値 テスト難易度 高 BusinessLogic 高 低 中 Repository 高 中 Controller 中 低 低 View 低 高
  52. 52. Viewのテストしたくねえな… Copyright 2017 @nuits_jp Slide 52
  53. 53. そもそもクラス単体テストの価値って? Copyright 2017 @nuits_jp Slide 53
  54. 54. システム サブシステム コンポーネント ソフトウェアのフラクタル Copyright 2017 @nuits_jp Slide 54 クラス サブシステム クラス コンポーネント クラス クラス ソフトウェア エンティティすべてに、ここまでの話は適用可能 • 要素間のインターフェースの文脈により、制御の流れと依存関係は制御可能 • 依存関係によって、安定性と柔軟性をコントロール可能 ソ フ ト ウ ェ ア エ ン テ ィ テ ィ
  55. 55. ソフトウェア エンティティ テスト価値 テスト難易度 障害発見 システム 高 高 遅い サブシステム コンポーネント クラス 低 低 早い ソフトウェア エンティティ別のテスト価値とテスト難易度 Copyright 2017 @nuits_jp Slide 55
  56. 56. 品質をあ げたい テストを 増やす テスト維 持コスト 上昇 維持でき なくなる 品質が下 がる テストのジレンマ Copyright 2017 @nuits_jp Slide 56
  57. 57. テストには計画的な戦略が必要だ! Copyright 2017 @nuits_jp Slide 57
  58. 58. などなど テスト戦略の立案 Copyright 2017 @nuits_jp Slide 58 プロジェクト 計画 ビジネス ユースケース システム ユースケース 機能要件 非機能要件 システム アーキテクチャ テスト戦略
  59. 59. What システム サブシステム コンポーネント クラス Why Who When Where Hoe How many How Much テスト戦略の立案 Copyright 2017 @nuits_jp Slide 59 先ほどのソフトウェア エンティティに対してmWnHで立てます
  60. 60. What システム Why ビジネス シナリオ(業務)の実現性評価 Who 開発サイドのテストチーム When 評価可能になり次第随時 Where システムテスト環境(Not顧客環境) How ビジネス シナリオに則って手動で How many 想定されるすべてのビジネスシナリオ How Much 全ビジネスシナリオで〇〇人月 テスト戦略:システム Copyright 2017 @nuits_jp Slide 60
  61. 61. ContinuousTestable Design まとめ
  62. 62. まとめ Slide 62Copyright 2017 @nuits_jp 1. 制御の流れに流されず、適切な依存方向にコントロールする 2. 依存方向によって安定性と柔軟性を、計画的に制御しましょう 3. 多面的な要素を考慮し、現実的なテスト戦略を立てましょう
  63. 63. ThankYou! Any Questions?

×