RSpecからpytestへ:戸惑いがちな書き方の対応チートシート

7分で読めるテック
最終更新:

副業のプロダクト開発でpytestを使ったユニットテストを書き始めたとき、それまでRSpecしか経験がなく、最初はかなり戸惑いました。「RSpecのbefore句はpytestで何を使えばいい?」「factorybotの代わりは?」という具体的な疑問が次々と出てきました。

同じようにRSpec経験からpytestへ移行する場面を想定して、よく使う書き方の対応をまとめます。

この記事は RSpec 3.xpytest 7.x〜8.x を前提にしています。RSpec公式とpytest公式はそれぞれ下記から参照してください。

逆引き対応表

やりたいことRSpec の書き方pytest の書き方
テスト前の準備処理before do ... end@pytest.fixture
特定テストだけ実行rspec spec/foo_spec.rb:12pytest -k "テスト名の一部"
ダミーデータ生成FactoryBotfactory_boy
デバッガの起動binding.prybreakpoint()
同じテストを複数の入力で回すshared_examplespytest.mark.parametrize

テスト実行:-k オプションで実行対象を絞る

RSpecでは実行ファイルと行番号を指定してテストを絞れます。

$ rspec spec/sample_spec.rb:12

pytestで同じことをするには -k オプションを使います。渡した文字列に 部分一致 するテストが実行されます。

$ pytest -k sample_test

sample_test を含む sample_test_1sample_test_2 もまとめて実行されます。完全一致ではない点に注意です。


テスト前の準備:before の代わりに fixture を使う

RSpecでは before 句でテスト前の処理を書きます。

before do
  # テスト前に実行したい処理
end

pytestでは @pytest.fixture デコレータを使って同じことを実現します。フィクスチャ関数をテスト関数の引数として受け取るだけで自動的に実行されます。

import pytest

@pytest.fixture
def user_data():
    return {"name": "test_user", "email": "test@example.com"}

def test_user_name(user_data):
    assert user_data["name"] == "test_user"

ダミーデータ:FactoryBot の代わりに factory_boy を使う

RSpecでは FactoryBot でダミーデータを定義します。

FactoryBot.define do
  factory :user do
    name { 'name' }
  end
end

pytestでは factory_boy で同じことができます。クラス定義の構文がやや違うだけで、発想はほぼ同じです。

import factory
from app.models import User

class UserFactory(factory.Factory):
    class Meta:
        model = User
    name = 'name'

デバッガ:binding.pry の代わりに breakpoint() を使う

Railsでのデバッグでは binding.pry が定番です。

require 'pry'; binding.pry

pytestでは Python 3.7 以降で使える組み込みの breakpoint() が現在の標準です。以前は pdb.set_trace() が使われていましたが、より短く書けます。

def test_something():
    result = do_something()
    breakpoint()  # ここでデバッガが起動する
    assert result == expected

また、--pdb オプションをつけてテスト実行すると、失敗したタイミングで自動的にデバッガが起動します。

$ pytest --pdb sample_test.py

複数パターンのテスト:shared_examples の代わりに parametrize を使う

RSpecでは shared_examples を使って同じテストを複数の入力値で実行できます。

pytestには pytest.mark.parametrize デコレータがあります。

import pytest

@pytest.mark.parametrize("input_val, expected", [
    (1, 2),
    (2, 4),
    (3, 6),
])
def test_double(input_val, expected):
    assert input_val * 2 == expected

入力と期待値のペアをリストで渡すだけで、全パターンが独立したテストとして実行されます。テスト名も自動的に test_double[1-2] のように区別されるので、失敗したときにどのパターンか一目でわかります。


移行直後は「RSpecなら一行で書けたのに」と感じる場面もありましたが、pytestはpytestで書き方が整理されていて、慣れると使いやすいフレームワークです。対応表を手元に置いておくと、最初の数日の戸惑いをかなり短縮できると思います。

質問・リクエストを送る

記事についての質問や、取り上げてほしいテーマがあればお気軽にどうぞ。いただいた質問はブログ記事として回答し、Q&Aページで公開することがあります。

このサイトについて

井上 周(Amane Inoue)の個人ブログです。技術・読書・ドラマ・旅・大学生活のことを書いています。