Humble Object
Humble Object at XUnitPatterns.com
非同期プログラミング - 非同期コードの単体テスト: テストを容易にする 3 つの解決策
Humble Object はインスタンス化することが難しいオブジェクトを、効率的にテストするための考え方です。(詳細は上記リンクを参照デス)
例えば、
・ビジュアルコンポーネント(ウィジェット、ダイアログなど)があるフレームワーク上にある場合です。
この時はUnitTestのためには、フレームワークで依存している全オブジェクトをUnitTest環境で構築する必要があります
・非同期に実行されるためテストが難しいアクティブオブジェクトです。
※アクティブオブジェクト:スレッド、プロセス、ウェブサーバなどのタスクの基点(エントリーポイント)となる関数を所有するクラスのオブジェクト
使い方
①すべてのロジックをテスト対象のコンポーネントから同期テストを介してテスト可能なコンポーネントに抽出します。
②①のコンポーネントは、テストが難しいコンポーネントのロジックを、動機かつパブリックで呼び出しできるサービスインターフェイスを実装します。
③その結果、Humble Object トコンポーネントは非常に薄いアダプタレイヤーになり、コードはほとんど含まれません。
④ハンブルオブジェクトがフレームワークによって呼び出されるたびに、それはテスト可能なコンポーネントに委譲されます。
例えば、アクティブオブジェクトRequestHandlerThread が初期化されることをテストしようとする場合、
下記をターゲットにして、runして数秒後にinitializedSuccessfullyがtrueを返すことを確認するようなコードを書くのですが、
processOneRequest内部に複雑な処理があって、initializedSuccessfullyの結果が設定される場合は安定したUnitTestを書くことができない。
(実際に数秒まったり、フレームワークの状態によってはエラーとなる)
public class RequestHandlerThread extends Thread { private boolean _initializationCompleted = false; private int _numberOfRequests = 0; public void run() { initializeThread(); processRequestsForever(); } public boolean initializedSuccessfully() { return _initializationCompleted; } void processRequestsForever() { Request request = nextMessage(); do { Response response = processOneRequest(request); if (response != null) { putMsgOntoOutputQueue(response); } request = nextMessage(); } while (request != null); } }
この例では簡単であるが、ストラテジーパターンを使用して、requestHandlerに処理を移譲している。
こうすると、requestHandlerを同期で試験する、スレッドに対しては処理が移譲できていることを確認すればOKとなる。
public class HumbleRequestHandlerThread extends Thread implements Runnable { public RequestHandler requestHandler; public HumbleRequestHandlerThread() { super(); requestHandler = new RequestHandlerImpl(); } public void run() { requestHandler.initializeThread(); processRequestsForever(); } public boolean initializedSuccessfully() { return requestHandler.initializedSuccessfully(); } public void processRequestsForever() { Request request = nextMessage(); do { Response response = requestHandler.processOneRequest(request); if (response != null) { putMsgOntoOutputQueue(response); } request = nextMessage(); } while (request != null); }
※
コードは参考ページから持ってきています。
英語自信ないため解釈誤りありましたらごめんなさい^^
負の継承への対応(2)
負の継承への対応を具体的にまとめていく
まず、共通性を侵すことなく可変性を扱う場合は、とくに問題なくあつかえるという前提です。
また、負の可変性は正の可変性と対応した技術で実現します。例えば、正の可変性をテンプレートで
実現した場合は、負の可変性はテンプレートの特殊化になります。
負の可変性を扱うメカニズム
・テンプレートの特殊化
共通の形:型によらず共通
template < class T> class Stack{ StackRep<T> *rep; } template < class T> class StackRep{ StackRep<T> *next; T* rep }
上記では、ポインタを持った構造体で管理しているが、整数型に対しては性能要件から動的配列はとれず、
要素数も決まっている場合があった場合は、テンプレートの特殊化で以下のようにできる。
template < > class Stack<int>{ static int rep[10000]; }
・デフォルト引数
アルゴリズムに強い共通性がある場合で、値に対して弱いバリエーションがあった場合、
(言い換えると、十中八九はこの値だけど、ごくまれにコレというようなケース)
・データのキャンセル
//テキストバッファを扱うクラス template <class CharSet > class TextBuffer{ public: Line<CharSet> getLine(const LineNumber&); private: LineNumber firstLine, lastLine; int currentLine; //はじめと最後の行、現在の行を扱えるとメンバ変数がいる }
1行だけ扱うテキストバッファが必要で、テキストバッファに対する機能を有している上記を継承する場合は、
複数行扱うメンバーは邪魔になる。
//ページ単位でテキストバッファを扱う template <class CharSet > class PageBuffer : TextBuffer<CharSet>{ public: } //1行単位でテキストバッファを扱う template <class CharSet > class LineBuffer : TextBuffer<CharSet>{ public: }
派生クラスで、基底クラスのメンバをキャンセルすることはできないため、可変性を外側にくくりだす。
ここでは、ページ単位でテキストバッファを扱うクラスと1行単位で扱うテキストバッファを以下のように定義する。
//まず、それぞれの実装を定義 template <class CharSet > class PageBufferRep : public TextBufferRep<CharSet>{ LineNumber firstLine, lastLine; int currentLine; } template <class CharSet > class LineBufferRep : public TextBufferRep<CharSet> { public: }
上記の実装に対して、クライアントへ提供するハンドルを以下のように定義する。
template <class CharSet > class TextBuffer{ public: Line<CharSet> getLine(const LineNumber&); protected: TexstBufferRep<CharSet>* rep;//最初の例と比較すると、所持していたデータメンバが抽象化されている。 TextBuffer(TexstBufferRep<CharSet>*); //コンストラクタでRepを割り当てる }
最終的にはこうする。
template <class CharSet > class PageBufferBuffer : public TextBuffer<CharSet>{ public: PageBufferBuffer () : TextBuffer<CharSet>(new PagedTextBufferRep){} } template <class CharSet > class LineBufferBuffer : public TextBuffer<CharSet>{ public: LineBufferBuffer () : TextBuffer<CharSet>(new LineBufferRep ){} }
図でいうと、
以下になってます
※
つかれたので、あとで整理しよう
負の継承への対応
■前置き
オブジェクト指向言語では、継承がサポートされているが、継承には2種類存在する。
・正の継承:Liskovの置換原則を守る継承
・負の継承:上記を守れないような継承
親クラスで定義されたモノは、継承関係で共通の領域とみなされる。これを侵害するようなことを指す。
負の継承の例
struct strrecvfd { #ifdef KERNEL union { struct file*fp; int fd; } #else int fd; #endif unsigned short uid; unsigned short gid; char util[8]; }
KERNELのシンボルの指定が基本である場合、KERNEL以外の場合には、負の可変性が生じている。
生じているのは、"fpが存在するという共通性が不要をされていること"です。
KERNELのシンボルの指定が基本である場合でない場合でも、負の可変性が生じている。
生じているのは、"fdの性質減っている"ことです。KERNELがない場合はfdのみで十分でしたが、KERNELの場合は、fd+fpで、もともとの
fdと同じ役割を果たしますので、fdの役割が一部減っています。
解決策を順にあげていく。
①負の継承に沿ってドメインをサブドメインに切り分ける。
②テクニックを用いる
①についてですが、上記の例では、
struct strrecvFP { union { struct file*fp; int fd; } unsigned short uid; unsigned short gid; char util[8]; } struct strrecvFD { int fd; unsigned short uid; unsigned short gid; char util[8]; }
②のテクニックについては、別途まとめます。
親子で明確に意味が異なるような場合は、テクニックでなんとかすのではなく、クラスをわけるというのが
正解かもしれない。
多分、②でなんとかできるのは"コードの再利用"の観点から継承を使用しているケースに限られるのではないかなと思う。
- 作者: ジェームス・O・コプリン,James O. Coplien,平鍋健児,金沢典子,羽生田栄一
- 出版社/メーカー: ピアソン桐原
- 発売日: 2009/12/01
- メディア: 単行本
- 購入: 3人 クリック: 41回
- この商品を含むブログ (6件) を見る
DI依存性注入
依存性の注入(Wikipediaから抜粋すると)
依存性の注入(いそんせいのちゅうにゅう、英: Dependency injection)とは、コンポーネント間の依存関係をプログラムのソースコードから排除し、
外部の設定ファイルなどで注入できるようにするソフトウェアパターンである。英語の頭文字からDIと略される。
コード中に依存関係を定義するコードがあると、ユニットテストがやりにくいという問題がある。
例えば、スクリーンに何かを描画するMainScreenが実際にディスプレイドライバーを触っているとする。
class MainScreen { void Disp() { DisplayDriver* drv = new DisplayDriver980() ; bool ret = drv.Setup(); if (FALSE = ret) { drv.Teardown(); return FALSE; } return TRUE; } };
上のコードを単体試験するときは、本物ドライバに依存しているため、ドライバーがインストールしている環境でないと動かないし、
ドライバーは大抵外部から提供されものであるため、自由に中を見れずエラーケースの試験を行うことが難しくなる。
こういうときは、MainScreenに対して依存するオブジェクトをMainScreenに依存する人が渡す方法がある。
これが依存性の注入(Dependency injection)。
class MainScreen { Driver& m_drv; MainScreen(Driver& drv) : m_drv(drv){} void Disp() { bool ret = m_drv.Setup(); if (FALSE = ret) { m_drv.Teardown(); return FALSE; } return TRUE; } };
利用者側が依存性を解決してあげる必要があるが(*)、これで特定のオブジェクトに対する依存性はなくなり、
依存するオブジェクトが特定のシグネチャに沿っていればよくなりました。
テンプレートを使うとよくわかるかと思います。下記コードでは渡すオブジェクトには継承関係がなくても
API仕様が同じであれば一切問題がないです。
template< class T > class MainScreen { T& m_depend; MainScreen(class T& depend) : m_depend(depend){} void Disp() { bool ret = m_depend.Setup(); if (FALSE = ret) { m_depend.Teardown(); return FALSE; } return TRUE; } };
(*)
依存関係を解決するのを利用者に渡しているため、利用者が必要な依存を判断する必要があります。
なので、、、
利用者に対して依存関係を知られたくない場合はこの方法は使えません。
例えば、外部の顧客に対して提供するAPIに対して、DIを利用するのは難しいかもしれません。
これは、依存するクラスをセットしてというより、モード切替といったAPIを用意する。もしくは
自分で動作環境を判断して内部で切り替えるといった仕組みを用意することが多いからかなっと思います。
ソフトウェアテスト(統合テストとユニットテスト) 覚書
■統合テスト
複数の層を1つに繋げるテストを指す。
できること
- UIテストで見つけにくい下位の不具合、最下層のユニットテストでは見つからない不具合を検出できる
→ 複数の部品がちゃんとつながっていることを確認できる
■ユニットテスト
※はじめにUIテストのデメリット
※ - 時間がかかる。一つあたりが数秒かかる場合を考えると数がおおくなればなるほど時間がかかる
※ - 問題あることの検知は容易だが、どこに問題があるか検知するのが難しい
できること
- 高速に実行できる、手軽に作成できる、迅速にフィードバックできる
- コードが正しく動くことを保証する。
- テストを書くことでコードに意図を伝えることができる
※いつまでやるか
→コードに自信がもてるまで。考えるべきことはすべて考えたかということがテストで明らかになる
※
ユニットテストのデメリットとしていかが起こりうる。
モックの泥沼:ユニットテストを頑張って書いた人なら一度はかんじたことがある。
「テストコード中の事前準備(モックの設定)が嫌におおい」→テストコードを書くのに時間がかかる。
原因は、テスト対象コード自体が保守しにくい構造になっているため、テストコードとテスト対象が密接に結びついているため。
解決策
1.リファクタリング:試験対象を直して、試験コードもきれいにする。
2.入出力にのみ注目する:テスト対象レイヤのインプットとアウトプットのみに注目して試験を行う。
両者ともに試験対象と試験コード間の結合度を下げる。2については、ポートとアダプタというアーキテクチャを利用できる。
ユーザーストーリマッピング覚書 2018/01/02
ユーザーストーリマッピングを読んでいるので覚書
■ユーザーストーリーって何?
・ユーザーストーリを作るのは何のためか
プロジェクトに関わる人間が作るものに対して共通理解を保つため
→ 実際のプロジェクトはできの悪い伝言ゲームのように皆が違うことを考えている。←防止する
本当に作らないといけないものを見極めるため
→ 実際のプロジェクトでは必要のないものも作ってしまっている。←防止する
・念頭に置くこと
誰の為に作るのか、何で必要なのかを常に考えよう
自分たちの作ったものがどう使われて、誰が喜ぶのかを考えよう
より多くの成果物を作るのではなく、より大きなインパクトを与えられるか考えよう
■どうすれば描ける?
・考えを整理する
- 自分の考え(アイデアや提供する機能)を表に出して整理する。
ホワイトボードやポストイットに書き出す。ポストイットの方が並び替えしやすい
・抜け漏れがないか確認する
- アイデアを利用する人の1日を声に出して言ってみる
表に出したアイデアで足りてないことがないか確認し、抜けていたら足す
→アイデアのマップを作る!
※
ストーリを洗い出す時は、細かいところを初めから見ないようにする。大きな枠で大筋を掴み。まず整理してから
細かい部分を洗い出す。
■洗練していくには?
・大きいマップを見やすくする
- 複数のアイデアをまとめる。←より上位もしくは抽象化された概念にまとめるとマップがスッキリしませんか?
・優先順位をつけましょう
- 依存関係から順番を見つけましょう
- 利用者に対してインパクトの大きいアイデアから作りましょう
・要らないものは削っていきましょう
- 優先度の低いものを作成する時間は削る。必要ないのであれば作らないとしましょう。
- MVPリリース:望まれる成果を実現できる最小の製品リリース
を見つけよう
SOLID原則:4つ目 インターフェース分離の原則(ISP:Interface Segregation Principle)
〇インターフェース分離の原則(ISP:Interface Segregation Principle)
クライアントにクライアント利用しないメソッドへの依存を強制してはならない
あなたがある機能を実現するモジュールを作成しているとして、そのモジュールが外部に公開しているIFを見るとメソッドが100個以上あって、複数クライアントから利用されているといったことはよくあると思います。
各クライアントはすべてのインターフェースを利用しているわけでなく、自分がほしい機能に関係するインターフェースを利用しているだけなので、機能ごとにインターフェースをグループ化して分けることで、クライアント利用するインターフェースを分離することができます。
こうすることで、クライアントは自分に関係あるインターフェースに対する変更した影響しなくなります。