Upgrade to Pro — share decks privately, control downloads, hide ads and more …

djangoRESTFrameworkのリファクタリング ~レガシーなコードの寿命を延ばすために~

Avatar for propro propro
October 27, 2023
320

djangoRESTFrameworkのリファクタリング ~レガシーなコードの寿命を延ばすために~

Pycon APAC

Avatar for propro

propro

October 27, 2023

Transcript

  1. who am I?
 Company: I am not employee I am

    freerance Works:djangoRESTframework/AWS X(twitter):@ProletariatPro
  2. who am I?
 Company: I am not employee I am

    freerance Works:DjangoRestFrameWork/AWS X:@ProletariatPro プログレッシブなプロレタリアート(労働者階級)という意 味です
  3. ここで話すこと ・自分が携わってるプロジェクト (ファイルの先頭に # coding: utf-8と書かれている)の寿命を延ばすため にやったこと ・fat controller問題 ・swaggerの修正

    ・request用のserializerの導入 ・unitテストの改善 ・その他意図不明の処理 ・blah-blah-blah….(等々) とチームで戦ってきた記録になります。
  4. テストがあれば何でもできる! ・With testing, anything is possible. ・Dengan ujian, segala-galanya mungkin.

    ・通過測試,一切皆有可能。 ・ดวยการทดสอบ ทุกสิ่งก็เปนไปได ・Dengan pengujian, segala sesuatu mungkin terjadi. ・테스트를 통해 무엇이든 가능합니다. ・ேசாதைனயால் எதுவும் சாத்தியம்
  5. Not SIMPLE!! class BreakfastView(APIView): def post(self, request, hogehoge): try: spam

    = request.data[“spam”] except KeyError: raise NotSpamError with transaction.atomic(): (A long code like a galaxy far, far away…) return Response(BreakfastSerializer(instance).data)
  6. Not SIMPLE!! class BreakfastView(APIView): def post(self, request, hogehoge): try: spam

    = request.data[“spam”] except KeyError: raise NotSpamError with transaction.atomic(): (A long code like a galaxy far, far away…) return Response(BreakfastSerializer(instance).data) request_bodyのチェックをviewで行っている 宇宙みたいなコードでした
  7. ではなかった...(宇宙が肥大化していく例) class BreakfastView(APIView): def post(self, request, hogehoge): try: spam =

    request.data[“spam”] if spam < 2: raise SpamLackError except KeyError: raise NotSpamError ham = request.data.get(“ham”, None) egg = request.data.get(“egg”, None) if ham and egg: raise HamEggError with transaction.atomic(): (A long code like a galaxy far, far away…) return Response(BreakfastSerializer(instance).data)
  8. テストがあれば何でもできる! ・With testing, anything is possible. ・Dengan ujian, segala-galanya mungkin.

    ・通過測試,一切皆有可能。 ・ดวยการทดสอบ ทุกสิ่งก็เปนไปได ・Dengan pengujian, segala sesuatu mungkin terjadi. ・테스트를 통해 무엇이든 가능합니다. ・ேசாதைனயால் எதுவும் சாத்தியம்
  9. 構成図 構成イメージ APP views models serializer.py tests urls.py hogehoge.py foo.py

    test_hogehoge.py test_foo.py hogehoge.py foo.py services hogehoge.py foo.py
  10. Service層導入後 class BreakfastView(APIView): def post(self, request, hogehoge): try: spam =

    request.data[“spam”] except KeyError: raise NotSpamError with transaction.atomic():    s = BreakfastService(spam) s.method1() instance = s.method2() return Response(BreakfastSerializer(instance).data) メソッド名を見ればだいたい何をやってるか分かるように なった。 テストがしやすくなった モデルからも分離できたので循環参照も減らせる!
  11. RequestCheck用のSerializer導入 class BreakfastView(APIView): def post(self, request, hogehoge): serialiser = BreakfastRequestBodySerializer(request.data)

    seriarizer.is_valid(raise_exception=True) with transaction.atomic():    s = BreakfastService(spam) s.method1() res = s.method2() return Response(ResponseSerializer(instance).data) チェック処理は全部Serializerに押し込んだ 型やその他のチェックも全部ここでやるよ!
  12. テストの現状 class BreakfastTest(TestCase): def test_ok(self): # prepare test c =

    client() res =c.post(path=”/api/breakfast”,data={“spam”:3, “ham”:1, “bacon”:2}) self.assertEqual(res.status_code, 200) self.assertEqual(レスポンスの確認)   # other check…. self.assertEqual(データベースの更新確認) self.assertEqual(その他諸々の更新確認)
  13. テストの現状 class BreakfastTest(TestCase): def test_ok(self): # prepare test c =

    client() res =c.post(path=”/api/breakfast”,data={“spam”:3, “ham”:1, “bacon”:2}) self.assertEqual(res.status_code, 200) self.assertEqual(レスポンスの確認)   # other check…. self.assertEqual(データベースの更新確認) self.assertEqual(その他諸々の更新確認) ・1回のテストで確認していることがたくさんある ・落ちた時に原因究明に時間がかかる ・可読性が低いのでなんのテストか確認する必 要がある レコード作成とかをたくさんやっていた
  14. テストの現状 class BreakfastTest(TestCase): def test_ok(self): # prepare test c =

    client() res =c.post(path=”/api/breakfast”,data={“spam”:3, “ham”:1, “bacon”:2}) self.assertEqual(res.status_code, 200) self.assertEqual(レスポンスの確認)   # other check…. self.assertEqual(データベースの更新確認) self.assertEqual(その他諸々の更新確認) ・1回のテストで確認していることがたくさんある ・落ちた時に原因究明に時間がかかる ・可読性が低いのでなんのテストか確認する必 要がある レコード作成とかをたくさんやっていた
  15. 簡単に直せて効果的(?)な方法 class BreakfastTest(TestCase): def test_正常パターン(self): BreakFastFactory() c = client() res

    =c.post(path=”/api/breakfast”,data={“spam”:3, “ham”:1, “bacon”:2}) with self.subTest(“レスポンスの確認”): self.assertEqual(res.status_code, 200) self.assertEqual(レスポンスの確認)   with self.subTest(“データベースの更新確認 ”) self.assertEqual(データベースの更新確認 ) with self.subTest(“その他のテスト”): self.assertEqual(その他諸々の更新確認) とりあえずまとまり毎に subTestにすることでリーダブ ルにする 日本語でOKなのでメソッド名でなんの確認か分かるように 事前にレコードを用意する必要がある箇所は FactoryBoyなどを使ってお手軽にレコード作成