こんにちは。マネーフォワードでQAを担当しているSugaです。2024年6月から、QAチーム内でプログラミングの基礎を学ぶ取り組みが始まりました。今回はその取り組みについてご紹介したいと思います。
1.はじめに
背景
マネーフォワードのQAチームは「特定の部署に所属せず、全体としてのプロダクトの品質向上を目指す」という最終目標があります。そのためには数多くのアプローチがありましたが、今回はプログラミングの理解向上にフォーカスすることになりました。その理由として、QAチームにはプログラミング経験の個人差が大きく、開発チームと実装する予定のコード設計の話をした際に、開発の意図とは異なる意味で受け取ってしまうなどのコミュニケーションミスが発生することがありました。最終的にはコミュニケーションを重ねた結果、誤解は解消しましたが、このような状況を改善すべく、プログラミング学習を始めることになりました。
取り組みの目的
開発チームとのコミュニケーション向上
プログラミングの知識を得ることで、技術的な議論をする際にQAと開発者の間でより正確な共通の概念を共有できるようになります。これにより、コミュニケーションが円滑になり、誤解やミスコミュニケーションを減らすことを目的としています。
テスト品質の向上
コードの構造やロジックの理解力を上げることで、UI上からは予測できなかった潜在的な不具合のパターンを予測できるようになります。そしてそれらをテストに盛り込むことで、より多くの不具合を未然に防ぐことを目指しています。
2.なぜQAがプログラミングを学ぶのか
QAの役割とプログラミングの関連性
現在のQAの基本的な役割
QAの役割は、プロダクトの品質を担保し、ユーザーに高品質な製品を提供することです。現在は主にマニュアルでのE2Eテスト計画・設計・実行、またプロセス改善などの活動に努めています。今回はその中でもテスト活動の品質向上にフォーカスしました。もちろん開発チームへテスト観点を共有し、開発チームのテストスキルを向上させる方法もあるかと思います。そのような取り組みも今後はできたらいいなと考えておりますが、まずはQAチームとして自身のテスト品質向上を第一に目指すことにしました。
プログラミングスキルがQAにどのように役立つか
ソフトウェア設計や実際のコードの理解に基づいて、より効果的なテストケースを設計し、テストの精度を向上させることが可能になります。
コードレビューに参加できるようになり、コードの品質やテストカバレッジを確認し、改善点を提案することができます。これによって、早い段階で不具合が検知できるようになります。
さらに、それ以外にもテストケースを自動化するコードを作成できるスキルを習得することで、手動テストにかかる時間と労力を大幅に削減し、テストの精度と一貫性を向上させることができます。また、テストの自動化が進むことにより、開発環境のアップデートやリファクタリングをより気軽に行えるようになることも目標としています。
3.学習プログラムの設計
学習プログラムの概要
2024年6月から週1回30分の講義と各自のHomeworkを組み合わせた学習プログラムを実施しています。このプログラムでは、コンピュータサイエンス学部の4年生レベルの学習内容を想定しています。
目標は、コーディングにあまり詳しくないQAが簡単な単体テストを書けるようになり、コードレビューを行えるようになることです。
学習において使用する言語はJavaです。Javaを選んだ理由は、日本のコンピュータサイエンス学部で広く採用されており、テスト関連のツールも揃っているからです。Javaを極めることが目的ではなく、テスト活動をする立場として必要な知識を得るためにJavaを採用しました。
使用した教材やツール
- 「Java[完全]入門」 著作者名:松浦健一郎 / 司ゆき
- 「単体テストの考え方/使い方 プロジェクトの持続可能な成長を実現するための戦略」著作者名:Vladimir Khorikov 編集者名:須田智之
- IntelliJ IDEA
カリキュラムの詳細
これまで実施された内容は以下のとおりです。
- Hello World
- プログラミング言語の基本制御(if分岐、ブール演算、ループ、関数)
- 本的なソートプログラム(バブルソート、クイックソート)
- 境界値テスト法
- ステートメントカバレッジと分岐カバレッジ
- オブジェクト指向プログラミングの基礎
- 例外とエラーの取り扱い
今後実施予定の内容は以下のとおりです。
- 単体テストを書く
- Postmanを用いてAPIテストを学ぶ
4. 学習の進め方
学習方法
講義形式で自分を含めた計3名のQAが受講しています。今回が初めての取り組みということもあり、少人数で実施しております。30分の講義で概要を学び、その後各自で課題を基に疑問点を深掘りします。
こちらは実際の講義風景です。その場では会話をしながら進めているため、このホワイトボードだけでは内容までは伝わりにくいかと思いますが、雰囲気だけでも感じ取っていただければと思います。
5.課題の実例紹介
以下では実際の課題を紹介します。基本的なソートプログラム(バブルソート、クイックソート)を学んだ際、「概念だけでなくコードも理解し、理解した上で何も見ずにこのコードを書けるようにする」という課題に対する勉強の記録です。 私は他のQAに比べコーディング知識に乏しいほうなので、一つ一つについて「これはどういう役割なんだろう?」「なぜ必要なんだろう?」と考えながら取り組みました。
Bubble sort
Bubble sortの概要
配列の最初の要素から隣接する要素を比較し、比較した要素が順序通りでない場合(例えば、昇順ソートで前の要素が後の要素より大きい場合)、それらの要素を交換する、というプロセスを繰り返す方法です。
ソートアルゴリズムについてはこちらのDevelopers Blog「ソートアルゴリズムについてまとめてみた」に詳細が記載されています。興味がある方はぜひご覧ください。
例題:"takahashi"をバブルソート
public class BubbleSort { public static void main(String[] args) { // ソート対象の文字列 String str = "takahashi"; // 文字列を文字配列に変換 char[] charArray = str.toCharArray(); // バブルソートの実行 char[] sortedArray = bubbleSort(charArray); // ソート後の結果を表示 System.out.println("Sorted string: " + new String(sortedArray)); } public static char[] bubbleSort(char[] array) { int n = array.length; boolean swapped; // バブルソートのメインループ for (int i = 0; i < n - 1; i++) { swapped = false; // 内部ループで隣接する要素を比較・交換 for (int j = 0; j < n - 1 - i; j++) { if (array[j] > array[j + 1]) { // 交換 char temp = array[j]; array[j] = array[j + 1]; array[j + 1] = temp; swapped = true; } } // 交換が行われなかった場合、ソートは完了している if (!swapped) { break; } } return array; } }
実行結果
Sorted string: aaahhikst
Quick sort
Quick sortの概要
配列の中から一つの要素をピボットとして選び、ピボットを基準にして、ピボットより小さい要素と大きい要素の2つのグループに分割するというプロセスを繰り返すことで要素を入れ替えていく方法です。
例題:"takahashi"をクイックソート
public class QuickSort { public static void main(String[] args) { // ソート対象の文字列 String str = "takahashi"; // 文字列を文字配列に変換 char[] charArray = str.toCharArray(); // クイックソートの実行 quickSort(charArray, 0, charArray.length - 1); // ソート後の結果を表示 (char 型の配列 charArray を新しい String オブジェクトに変換) String sortedStr = new String(charArray); System.out.println("Sorted string: " + sortedStr); } // クイックソートのメインメソッド public static void quickSort(char[] array, int low, int high) { if (low < high) { //ソート範囲が1つ以上の要素を含む場合にのみソートを実行 int pivotIndex = partition(array, low, high); //配列を2つの部分に分割 quickSort(array, low, pivotIndex - 1); //配列の左側部分(`low` から `pivotIndex - 1`まで)を再帰的にソート quickSort(array, pivotIndex + 1, high); //配列の右側部分(`pivotIndex + 1`から`high`まで)を再帰的にソート } } // 配列を分割する`partition` メソッド private static int partition(char[] array, int low, int high) { char pivot = array[high]; //配列の最後の要素をピボットとして選択 int i = low - 1; // `i` はピボットより小さい要素の最後のインデックスを追跡 初期値は `low - 1` for (int j = low; j < high; j++) { // `low` から `high - 1` までの要素をループでチェック if (array[j] < pivot) { // trueの場合 `i` をインクリメントしその要素と `i` 番目の要素を交換 i++; swap(array, i, j); // 要素を交換するために `swap` メソッドを呼び出す } } swap(array, i + 1, high); // ループが終了したらピボットを正しい位置に移動 return i + 1; // ピボットの最終的な位置を返す } // 交換 private static void swap(char[] array, int i, int j) { char temp = array[i]; array[i] = array[j]; array[j] = temp; } }
実行結果
Sorted string: aaahhikst
6.実際に取り組んでみた感想
つまずいた部分
ステートメントカバレッジと分岐カバレッジについて学習をしたとき、カバレッジ率が高ければ必ずしも品質が保証されるわけではないということを学びました。これを踏まえて、QAとして今後どのようにして品質を担保していくべきかについて考えるようになりました。カバレッジ率はコードの特定の部分が実行されたかどうかを示すだけであり、実行結果の正確性や重要なケースのカバーを保証するものではありません。単体テストで例外が発生するケースがテストされているか、オンポイントとオフポイント1を抑えているかなど、様々な点をQAとして確認する必要があります。もちろん従来のE2Eのテスト活動もありますが、例えばUI上の入力からは確認できない内部のゼロ除算などはE2Eテストでは担保できないため、単体テストで保証する必要があります。そういったケースが考慮されているかの確認をQAとして今後できるよう、どのような点を確認すべきか、質問を交え他のQAメンバーと相談しながら勉強を進めました。
学びがあった部分
上記とも関連しますが、プログラミングの基礎を学ぶことで、単体テスト・APIテスト・E2Eテストで担保する内容が以前より具体的にイメージできるようになりました。 今後QAとしてより効果的なテスト活動をするため、単体テストやAPIテストについても開発チームと協力をして一緒に取り組んでいきたいと思います。
7. まとめ
まだまだ分からないことは多いですが、この取り組みを始める前と比較し、APIテストやコーディングに対する心理的ハードルが下がり、自分なりにプロダクトをより理解しようと試みるようになりました。今後も継続的に学習を続け、成長していくことで、より高品質な製品をユーザーに提供できるよう努めていきたいと思います。また、今回参加できなかった他のQAメンバーへの展開も視野に入れ、全社的な品質向上に貢献していけたらと思います。最後に、今回はQAが初心者向けのプログラミング学習をしている取り組みについてご紹介しました。次回は、この学習を経て実務にどのように活かしているのか、QAがコードをどのような観点で見ているのかについてお伝えできればと思います。次回の記事もぜひご期待ください。
- 【1】オンポイントは仕様で指定されている境界値。オフポイントは境界値に隣接し、オンポイントと逆の結果となる値。↩