C2カバレッジについて
カバレッジ基準のおもにC2カバレッジについて調べ直したのでまとめてみた。
カバレッジ基準とは
制御フローテストで着目する要素を「命令文」「分岐」「条件」のうち着目してカバレッジを計測する要素のことをカバレッジ基準と言います。
この要素で主なものは3つあります。
事実
ステートメントカバレッジ
「命令文」に着目して全ての命令文を最低一度は通れるようにテストします。
ディシジョンカバレッジ
「分岐」に着目して全ての分岐を最低一度は通れるようにテストします。ステートメントカバレッジを包含しています。
コンディションカバレッジ
「条件」に着目して全ての条件を最低一度は通れるようにテストします。
単純条件カバレッジと複合条件カバレッジ
複数の条件を組み合わせた条件分岐がある場合に、個々の条件の真偽のみの組み合わせを網羅することを単純条件カバレッジという。ディシジョンカバレッジを包含していないし、ステートメントカバレッジも包含していません。
複数の条件を組み合わせた条件分岐がある場合に、そのすべての組み合わせを網羅することを複合条件カバレッジという。ディシジョンカバレッジを包含しています。
C2カバレッジが単純条件カバレッジと複合条件カバレッジのどちらのことを差しているかわからなかった。
同じような疑問を持った人がいたようだが、調べてみることにした。
コードカバレッジについて、また考える - その1 - ソフトウェアの品質を学びまくる2.0
Webサイトでの説明
C2を単純条件カバレッジとするWebサイトと複合条件カバレッジとするWebサイトがある。
書籍による説明
持っている書籍のみを一通り調べた。
- ソフトウェアテストの教科書
- ソフトウェアテスト実践ワークブック
- ソフトウェアテスト技法 自動化、品質保証、そしてバグの未然防止のために
- はじめて学ぶソフトウェアのテスト技法
- 知識ゼロから学ぶソフトウェアテスト
- ソフトウェアテスト293の鉄則
- テスト駆動開発
- ソフトウェアテスト教科書 JSTQB Foundation
- 記載なし
- システムテスト自動化 標準ガイド
- 記載なし
- ソフトウェアテスト技法ドリル
- 記載なし
- 実践テスト駆動開発
- 記載なし
(追記) * ソフトウェアテストと導入・移行 * 複合条件カバレッジが書かれており、単純条件カバレッジについての記載はない。 * プログラミング現場の単体テスト * 珍しく単体条件カバレッジのみが書かれている。単体条件カバレッジがディシジョンカバレッジことも書かれている * ソフトウェアテスト入門 押さえておきたい<<要点・重点>> * ディシジョンカバレッジまでは書いてあったがコンディションカバレッジについての記載は見つけられなかった * 知識ゼロから学ぶソフトウェアテスト * ディシジョンカバレッジまでは書いてあったがコンディションカバレッジについての記載は見つけられなかった * ソフトウェア品質を高める開発者テスト * C0、C1までは書いてあったがコンディションカバレッジについての記載は見つけられなかった
C2が何かが書かれていなかった。
Google ブックスによる検索
いくつかをピックアップした。全文読めないので若干何とも言い難い。
所感
C2カバレッジは、単純条件カバレッジと複合条件カバレッジのどちらかをはっきりさせることはできなかった(諦めた)。 単純条件カバレッジと複合条件カバレッジを計測できるツールを私は知らないし、計測するつもりもない。他の人も同様に計測する気はないのだろう。つまり、定義がどっちでもいいのだ。
まとめ
仕様を読む技術
走り書き。根拠はない。
まだ小さな開発チームなので社内のプルリクエストを流し読み程度に全て見るようにしている。コードの品質は人によってさまざまあるのは当然だが、仕様の理解度に差があるように感じている。それは人によって異なっていて、常に理解度の高い人と低い人がいる。
仕様を読むためには、主に3つの要素があるのではないかと考えた。
- 前提知識
- 環境要因
- 完成の定義
前提知識
システム開発だと0から作ることは少なく、多くの場合は追加開発になる。要求は何か、既存のシステムがどうなっているか、技術的に何が使われて何を使えばいいのか、がわかっている必要がある。
例えば、「ルービックキューブに黒い面を追加する」という追加開発があったとする。要求は「他の6面と同様な7面目を追加する」ということになり、既存のシステムは「3x3マス、立方体の組み合わせ、回転できる」等である。技術的な部分は「回転機構、各パーツの形」等がある。この例の要求は明らかに矛盾していることに気がつく。しかし、システムを作るときはよくあり、矛盾があることに気がつかない人がいる。これは前提知識がないために起こるのではないかと思われる。
前提知識は単に時間をかけて知っていくしかないように思う。
環境要因
仕様のできや、忙しくて読む暇がない、期日が迫っている、疲労等で仕様を読む能力が減少する。
読む能力が低下する要素を減らすようにすべきだろう。
完成の定義
仕様に完成の定義が書いてあれば良いのかもしれないが、書いていない場合は完成の定義を考えられると仕様を読んだときの質が変わってくる。完成の定義が曖昧なまま書かれたコードは曖昧な部分が透けて見えることがたまにある。
完成の定義は「仕様を満たしていることがわかるテストを作成し問題なく通ること」と考えている。テストは自動でも手動でもかまわない。
最後に
「他にこんな要素があるのではないか?」とかがあればコメントしてほしい。
事例: プルリクを誰も見てくれない
エンジニアが数名という時に、Androidアプリをレビューする人がいないという事態が度々発生した。作ったプルリクエストは滞留してなかなかマージされない。取った解決策は、Githubにプルリクが作られアサインが行われたら該当の人に通知を行うというものだった。通知はメールやSlackに配信された。
結果としては、効果はなかった。その時にプルリクを見れる人は1人しかおらず、その人は会議に出ずっぱりだった。実質的に、リアルタイムにプルリクを見れる人は誰もいなかったのだ。通知を送ってもレビューがされないのも当然だった、誰も見れないので。
Androidのレビューできる人を増やすということで解決した。専門でない人にわかるような内容のプルリクにしたり、基礎知識を教育した。
現実を直視しないと何も解決しない。
TableHeaderViewのサイズをいい感じにする
UITableViewのtableHeaderView(セクションごとのヘッダーではない)は経験的にオートレイアウトが効かないので、状態によってTableHeaderViewの高さを変えたい厄介になります。この記事ではサイズをいい感じに調整する方法を紹介します。TableFooterViewも同様です。
やり方
TableHeaderViewには一つだけViewを置いてください。このVIewのことを便宜上BaseViewと呼ぶことにします。BaseViewはUIViewでもUIStackViewでも構いません。重要なことは1つだけにすることです。
BaseViewを上と左右に0でAutoLayoutを設定して、BaseViewの中身によってBaseViewの高さが決まるようにしてください。
ViewControllerに以下のコードを追加してください。
override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() if let headerView = tableView.tableHeaderView, headerView.subviews.count == 1, let baseView = headerView.subviews.first { headerView.frame.size.height = baseView.bounds.size.height tableView.tableHeaderView = headerView } }
終わりです。
なぜいい感じになるのか
厳密には違うかもしれないが、viewDidLayoutSubviewsはオートレイアウトによって調整が行われるたびに呼び出されるメソッドです。BaseViewの高さがオートレイアウトによって変更されるたびに呼び出されてBaseViewの高さとTableHeaderViewの高さを同じにすることで、TableHeaderViewの高さを調整しています。
BaseViewの高さがTableHeaderViewに影響されないように、下にオートレイアウトをつけないのはそのためです。
ソースコードをお焚き上げしました
エンジニアは意外にも信心深いところがある。サーバーをお祓いしたり、デバッグ神社を建てたり、リリースするときにお祈りする。
iOS、Androidアプリを全面リニューアルと一部サービス終了があったので、今までのソースコードへの感謝の気持ちを込めてお焚き上げをすることにした。
今まで稼いだコードへの感謝
弊社では、アプリを全面リニューアルすることにした。
- 外注だったのを内製化した
- Objective-CとJavaで書かれていたのをSwiftとKotlinに置き換えた
- モダンなアーキテクチャにした
外注を行っていたのを内製化するにあたって、中途採用が行われチームができた。人を雇って改善を行っていこうと思うほど、重要なアプリになっていた旧アプリはそれだけの価値があったということになる。
内製化を行ったチームは、初めから内製化をし始めたわけではない。初めにやったことは内製化の価値を示すためにチームは新しいサービスを立ち上げた。サービスは今までには考えられないほどのスピードで開発されチームの価値が示された。ただ、そのサービスも期間限定で好評のうちに終了した。
私たちは、今まで稼いでくれていたが不要になったソースコードに感謝の念を示したいと考えた。検討した結果、お焚き上げをすることにした。
準備
ソースコードの持ち出しの許可をとる
即座にお焚き上げをするとはいえソースコードを無断で持ち出すのは問題がある。書類整理破棄を名目でCTOに持ち出しを打診したところ「お焚き上げをアンオフィシャルでやるならいいよ」と許可をもらった。お焚き上げは有志で行うことになった。
場所決め
IT系の神社といえば神田明神だが、ホームページにはお焚き上げに関する記載がなかったのと、 神札·御守·熊手·破魔矢等のみとのツイート があった。優先順位をつけて探すことにした。
- 目の前でお焚き上げをしてもらえること
- 場所(都内〜遠くても関東)
- 予算(〜数万円程度)
同僚に探してもらったところ、つてで 戸越八幡神社 で行うことになった。ダンボール1箱程度で10,000円だった。割り勘で払うことにした。
印刷
前日に印刷を行った。その辺にあったMacbook Proの箱に詰めた。
そこまで多いソースコードだと思っていなかったが4pt2ページづつ両面印刷して、ほとんど米粒程度のフォントサイズにで印刷されてなんとか見える程度だった。しかしそれでも500枚程度になったので、もしやる機会がある人は気をつけてほしい。Macbook proの箱はちょうどいいサイズの段ボールでできているので、燃えるしどこの会社にもあるのでおすすめです。
当日
10時に戸越八幡神社に集合した。眠い。昇殿参拝を行って継往開来で祈祷をおこなってもらった。お清めされたドラム缶でお焚き上げをおこなった。 燃えていくソースコードを眺めていると「終わった」という気持ちに満たされていった。
おわりに
リニューアルやサービスの終了時にお焚き上げするのをお勧めします。
最後に、感謝の念を込めながらGithubのアーカイブのボタンを押して幕を閉じた。
仕事しながら考えていること
プロダクトの価値
プロダクトの価値をできるだけ高速で高めることが大切だと思っている。プロダクトの価値を高めることができれば、手段を選ばない。違法なことや倫理に反すること以外はやる。
プロダクトの価値は、主な評価軸として主に3つあり上の方が重要だと思っている。
* 企業への利益
* ユーザーへの価値
* プロダクトのリリース速度
企業の利益がなければプロダクトが継続できず、ユーザーへの価値がなければ企業の利益が得られない。両方を高めるために、プロダクトのリリース速度を高めます。
企業の利益
ようわからん。雰囲気でやってる。
ユーザーへの価値
全てのユーザーがどう感じるか。仮想のユーザー10人がどう見るか想像しながら作っている。実際、出してみないとわからないところがあるので、さっさと出すことを考えている。事前にユーザーへの価値が高いことがわかっている方が好ましい。事前調査やスモールスタートして、大きくする前に調べるべきだと思う。
プロダクトのリリース速度
チーム開発の本を読んだ方がいいとは思うが、書いておく。
失敗することはどうでもいい
失敗は誰にでもあるし、自分もする。失敗したことをくどくど言ってもしょうがない。重要なのは、できるだけ高速で治す事と失敗を繰り返さないことである。怒ったとしても今困っているユーザーは困りっぱなしなだけです。
失敗しても直せればいいと考えているので、他人が失敗しそうになっていてもほっといたりします。失敗しないと聞かないし。
人間が頑張らない
途中で人間が挟まるから失敗するのであって、できるだけ挟まないようにしたり、ツールによるチェックを挟むことで失敗をできるだけ避けるようにするべきです。考えるべき順番としては、
- 自動化
- チェックツールを導入する
- チェックリストを使う
- 気持ち(気をつけるとかそういうの)
です。気持ちで解決しないようにする。
自分一人で頑張らない
自分一人で頑張っても割とどうしようもないことが多々ある。たとえ、毎日4時間残業したとしてもせいぜい1.5倍にしかならない。休日出勤したとしても2倍程度だろう。なら、会社としては2人に増やして分担した方が一人の負担は少ない。分担できるように考えた方が得策なので、一人では頑張らないし、時間的に不可能を可能にしようとしないように考えている。チームの仲間や外注する等をしていった方が良い。
頑張らないためには、自分の実力を把握する。限界や単位時間で自分ができる作業量を把握しておく。
他人に頑張らせない
ほとんど「自分一人で頑張らない」と同じだが他人にも頑張らせないように気を使う。分担できるように考えさせる。一人でやりたいというならほっとく。
作業を分割する
作業の総量の把握、分担、順番に片付けるために分ける。
可能な限り分割して順番に行っていく、一つ一つ片付けで手戻りを少なくする。
手戻りしても、深呼吸して忘れる。
アジャイルソフトウェア開発宣言の左側もちゃんとやる
左側をしないとか、重要視しないとか勘違いされがちだが、重視しないだけでしないわけではない。
プロセスやツールで解決できることがあるのであれば、プロセスやツールで解決すればいい。
ドキュメントを書くべきであれば書く。
チーム
チームは自分たちが担当しているプロダクトに責任をもつ必要がある。プロダクトに責任を持っていることによって権利を得るべきだと思う。
チームはいくつかの権利を持つべきである * 独立した意思決定をできる * 管理できるプロダクトの個数や大きさを保つ * プロダクトを全て開発・運用できる
トラックナンバーを気にする
自分が癇癪起こして辞める 誰かが病気になる可能性があるので、作業途中でも引き継げるようにする。チームで作業するのも同様に作業途中で引き継げるようにするため。少なくとも引き継いだ時の作業ロス時間を1時間未満にしたいと考えていて、少なくとも1日未満を保った方がいい。
定期的な作業は少なくとも2人以上できるようにして、ドキュメントを書いておく
iOSアプリをリニューアルした時のアーキテクチャ
最近、iOSアプリをリニューアルしたのでその時に工夫した内容を書いておこうと思う
前提
- 仕様書はない
- iOSアプリでObjective-Cで書かれている
- Swiftに全てのコードを書き換える。SwiftUIは使わない。
- 複数アプリが一つのソースコードでプロジェクトで管理されている
最後の部分はあまりないがほとんど同じアプリがコピペで別のリポジトリで管理されるよりはマシな状況である。二つのBuild SettingsでTarget Membershipで分けるのは、読み取りづらいものになっていた。
解決したい問題
- 典型的なBaseViewController 問題がある
- FatViewControllerでもある
巨大なViewControllerにベタが記されていて、何が行われているか見通しが悪くなっていた。
解決方法
ViewControllerから処理を剥がすためにフレームワークに分けて、それぞれの役割をはっきりとさせたかった。単純に分けただけでは役割とは異なる箇所にコードを書いてしまうことができるため、フレームワークごとに依存するライブラリを限定して、役割に合っていないフレームワークで書くことを難しくした。さらに、フレームワークごとの依存関係を限定することで、間違った使い方をできないようにした。
3つのフレームワークとアプリに分ける
DomainとInfrastructure、PresenterのフレームワークとそれをまとめるApplicationのアプリに分けることにした。
DomainとInfrastructure、Presenterはそれぞれの役割に集中できるようにフレームワークに分離して、依存する対象をはっきりさせるよにした。たとえば、InfrastructureからPresenterは依存してすることはプロジェクトの設定で絶対にできないようになっている。
Applicationは、複数のアプリの差分(アプリのアイコン等)を持っており、コードの量は最小限になるようにした。
Domain
ドメイン駆動設計で言うところのDomainのこと。ビジネスロジックを可能な限りModelに書くようにした。Respositoryはprotocolになっている。Respositoryを経由してModelを取得するようにしている。 DomainRegistoryは、全てのRepositoryのオブジェクトを持っているか生成するのが役割です。 DomainはRxSwiftのみに依存している。SwiftUIを採用していればRxSwiftに依存せずに別の設計になっていたかもしれない。
import RxSwift public struct XxxModel { let value: String } public protocol XxxRepository { func load() -> Obserable<Model> } public protocol DomainRegistory { var xxxRepository: XxxRepository { get } }
Presenter
Presenterは、主にDomainとUIKitに依存している。
DomainRegistoryが持っているRepositoryを使ってDBや通信の操作するようにしました。これによって、Presenterは本番環境から取得されているか、ステージング環境か、モックかを気にすることなくUIに集中できるようにしました。
Infrastructure
Infrastructureは、主にDomainとDB、API通信クライアントに依存している。
RepositoryImpがRepositoryの実装になっている。通信やキャッシュ、Entityへの変換を実装している。
Application
ApplicationはDomainRegistoryの実装であるDomainRegistoryImpを作成と、環境変数の注入が主な役割である。各アプリ特有の設定や本番とステージング環境の切り替えを行なって、InfrastructureやPresenter現在の実行環境がどうなっているかを気にしなくていいようする役割である。
実際どうだったか
全体の見通しは非常に良く、どこになくを書くべきかが明確なので、失敗しづらい構造になっていました。
今回のアプリはAPI通信で取得したデータをキャッシュして表示する画面がほとんどだったので、ほとんどがアーキテクチャに合わせて構築するだけの部分が多かったため、大きく問題になることはありませんでした。ユーザーの入力が複雑になった場合にRepositoryが複雑になるかはわからなかった。
やらなかったこと
- 流行りDI コンテナ
- ここには書かなかったが、RxSwift等を導入したため一同導入するものを減らしたかった
- SwiftUI
- iOS12をサポートすると決めていたため