Halcyon Days

IT × 移住 × ファイナンス

MENU

Pythonで始めるテスト駆動開発

はじめに

本日のタスクでテストコードを書いていたときに、「もしかしてテストから先に書いた方が楽じゃね?」と思うようになりました。

「メソッドを書く→テストコードを書く→改修する」というフローで開発していたのですが、メソッドをすぐ実行でき、かつ段階的に書く→テスト→改修というフローをすぐ回せるのでいいなと思ったわけです。

もっとよく知りたいと思ったのでAIさんに聞きながら実践してみました。

ワイ「テスト駆動開発について教えてくれ」

エンジニアさん、こんにちは。

今日、開発をしていた時に「テストから書いた方が楽なんちゃう?」と思って調べていたら「テスト駆動開発(TDD)」という開発手法で叶えられるっぽいんで、テスト駆動開発について詳しく教えくれへん?

AI「ええで。Pythonのサンプルコードも付けたるわ」

こんにちは。テスト駆動開発(Test-Driven Development、TDD)は、ソフトウェア開発の手法の一つです。TDDでは、まずテストを書いてからそのテストを満たすコードを書くというサイクルを繰り返します。以下に、TDDの基本的な流れとPythonを使った簡単な例を紹介します。

TDDの基本的な流れ

  1. テストを書く:最初に、これから実装する機能に対するテストを書きます。このテストは、まだ存在しない機能をテストしようとするため、最初は失敗します。
  2. コードを書く:次に、そのテストをパスするために最小限のコードを書きます。この段階では、コードの美しさや効率性は二の次です。
  3. テストを実行する:書いたコードがテストに合格するかどうかを確認します。テストが合格したら、次に進みます。
  4. リファクタリングする:テストに合格したコードをきれいに整える段階です。リファクタリングを行ってもテストが合格することを確認します。

Pythonを使ったTDDの実践例

それでは、簡単なPythonの例を使ってTDDのプロセスを見てみましょう。ここでは、簡単な計算機能(例えば、足し算を行う関数)を実装するとします。

1. テストを書く

まず、足し算を行う関数 add(a, b) をテストするためのコードを書きます。このテストでは、関数 add が2つの数値を正しく足し合わせるかを確認します。

# test_calculator.py

import unittest

class TestCalculator(unittest.TestCase):
    def test_add(self):
        from calculator import add
        self.assertEqual(add(2, 3), 5)
        self.assertEqual(add(-1, 1), 0)
        self.assertEqual(add(-1, -1), -2)

if __name__ == '__main__':
    unittest.main()

このテストは、calculator.py モジュールに add 関数が存在し、その関数が正しく動作することを期待しています。

2. コードを書く

次に、テストがパスするための最小限のコードを calculator.py に書きます。

# calculator.py

def add(a, b):
    return a + b

3. テストを実行する

ターミナルでテストを実行してみます。

$ python -m unittest test_calculator.py

すべてのテストがパスすれば、次に進みます。

4. リファクタリングする

今回はコードが非常に簡単なのでリファクタリングの必要はありませんが、より複雑な場合はコードを整理し、再度テストを実行して問題がないことを確認します。

ワイ「なんとなくわかったわ。とりあえずやってみるからコードレビューして」

FizzBuzzをTDDで書いてみる

1から100のうち、3で割り切れる場合に「Fizz」を、5で割り切れる場合に「Buzz」を、3と5で割り切れる場合に「FizzBuzz」と出力し、それ以外の場合は数字を出力する。

1.テストを書く

import unittest

class TestFizzBuzz(Unittest.TestCase):
    def test_fizzbuzz(self):
        from function import fizzbuzz
        self.assertEqual(fizzbuzz(1), '1')
        self.assertEqual(fizzbuzz(3), 'Fizz')
        self.assertEqual(fizzbuzz(5), 'Buzz')
        self.assertEqual(fizzbuzz(7), '7')
        self.assertEqual(fizzbuzz(15), 'FizzBuzz')
        
        
if __name__ == '__main__':
    unittest.main()     

2.コードを書く

def fizzbuzz(int):
    if int % 3 == 0 and int % 5 == 0:
        return "FizzBuzz"
    elif int % 3 == 0:
        return "Fizz"
    elif int % 5 == 0:
        return "Buzz"
    else:
        return int

AI「直しといたで〜〜TDD以前に君のコードあかんすぎやわ」

ワイ「すみませんした」

コードレビューを行います。いくつかの修正点がありますので、以下に詳しく説明します。

テストコードの修正点

  1. クラス名の綴り: Unittest ではなく unittest が正しい綴りです。
  2. アサーションの修正: アサーションの部分で、文字列と整数の比較を行っている部分があります。文字列に統一する必要があります。

実装コードの修正点

  1. 関数名の引数名: intPythonの組み込み型の名前なので、他の変数名に変更します。
  2. 関数の戻り値の型: return する値をすべて文字列に統一します。

修正後のコード

以下が修正後のテストコードと実装コードです。

テストコード

import unittest

class TestFizzBuzz(unittest.TestCase):
    def test_fizzbuzz(self):
        from function import fizzbuzz
        self.assertEqual(fizzbuzz(1), '1')
        self.assertEqual(fizzbuzz(3), 'Fizz')
        self.assertEqual(fizzbuzz(5), 'Buzz')
        self.assertEqual(fizzbuzz(7), '7')
        self.assertEqual(fizzbuzz(15), 'FizzBuzz')

if __name__ == '__main__':
    unittest.main()

実装コード

def fizzbuzz(n):
    if n % 3 == 0 and n % 5 == 0:
        return "FizzBuzz"
    elif n % 3 == 0:
        return "Fizz"
    elif n % 5 == 0:
        return "Buzz"
    else:
        return str(n)

修正後のコードでは、テストコードと実装コードの両方が正常に動作し、TDDの流れを正しく実践できています。以下のポイントに注意して見直しました:

  1. アサーションの一貫性: アサーション部分での型の一貫性を保つ。
  2. 関数の引数名と戻り値の型: 組み込み型の名前を避け、戻り値を文字列に統一。

これらの修正により、コードがより明確でエラーの少ないものとなります。今後もTDDを実践しながらコード品質を向上させていってください。

ワイ「・・・ありがとうございました」

AI「TDDについてまとめるで」

TDDには、以下のような利点があります:

  • バグを早期に発見:テストが先にあるため、バグが早期に発見できます。
  • 設計の改善:テストを書くことで、設計に対する理解が深まり、より良い設計を促進します。
  • リファクタリングの安全性:テストが存在するため、リファクタリング時にコードが壊れていないことを確認できます。

以上がTDDの基本的な考え方とPythonを使った簡単な実践例です。ぜひ試してみてください。

参考文献

TDDを実践することで、コードの品質と開発効率を向上させることができます。ぜひこの手法を活用してみてください。