factory_boy: SubFuctory で ForeignKeyフィールドを初期化する

factory_boy はテストデータを簡単に作るためのライブラリです²。ForeignKey フィールドのインスタンスのデフォルトを定義するには、SubFactory を使う方法があります¹⁴。例えば、以下のように書けます。

1
2
3
4
5
6
7
8
class PhoneContactFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.PhoneContact

class CoopFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.Coop
    phone = factory.SubFactory(PhoneContactFactory)

この場合、CoopFactory を使って Coop インスタンスを作ると、PhoneContactFactory も使って PhoneContact インスタンスを作り、そのインスタンスを Coop の phone フィールドにセットします。

もしくは、SelfAttribute を使う方法もあります⁴。例えば、以下のように書けます。

1
2
3
4
5
6
7
8
class SubtitlesFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.Subtitles

class RecordingFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.Recording
    subtitles = factory.SubFactory(SubtitlesFactory, language=factory.SelfAttribute('..language'))

この場合、RecordingFactory を使って Recording インスタンスを作るときに language パラメータを指定すると、その値が SubtitlesFactory の language パラメータにも渡されます。

ソース: Bing との会話 2023/4/30

はい、他にも方法があります。exclude を使う方法があります⁴。例えば、以下のように書けます。

1
2
3
4
5
6
class RecordingFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = models.Recording
        exclude = ('language',)
    language = 'en'
    subtitles = factory.SubFactory(SubtitlesFactory, language=factory.SelfAttribute('..language'))

この場合、RecordingFactory を使って Recording インスタンスを作るときに language パラメータを指定しなくても、デフォルトの値 ’en’ が SubtitlesFactory の language パラメータに渡されます。しかし、language パラメータは Recording のモデルには存在しないので、exclude に指定して除外します。

ソース: Bing との会話 2023/4/30

exclude の方法は、factory_boy の機能の一つです。exclude に指定したフィールドは、Factory の内部で使われるだけで、モデルのインスタンスを作るときには渡されません。 その代わりに、SelfAttribute で他のフィールドに参照させることができます。 これは、モデルには存在しないが、Factory で使いたいフィールドを定義するときに便利です。

create の直後に refresh_from_db を行うには、post_generation で self.refresh_from_db() を呼ぶ方法があります⁴。例えば、以下のように書けます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
class UserFactory(factory.django.DjangoModelFactory):
    class Meta:
        model = User
    username = factory.Sequence(lambda n: 'user%d' % n)
    email = factory.LazyAttribute(lambda o: '%s@example.com' % o.username)

    @factory.post_generation
    def refresh(self, create, extracted, **kwargs):
        if not create:
            return
        self.refresh_from_db()

この場合、UserFactory を使って User インスタンスを作るときに、create の直後に refresh_from_db が実行されます。

ソース: Bing との会話 2023/4/30