برنامه نویسی

تزریق وابستگی پیشرفته در اکسیر با Rewire

در آخرین پست خود، بررسی کردیم که چگونه Dependency Injection (DI) یک الگوی طراحی قدرتمند است که می تواند تست های ExUnit ما را بهبود بخشد.

در این مقاله، با تمرکز بر کتابخانه Rewire برای پروژه‌های Elixir، عمیق‌تر به موضوع DI در اکسیر خواهیم پرداخت.

ما مفاهیم اصلی Rewire، نحوه شروع کار با آن و مثال های عملی را پوشش خواهیم داد. همچنین نحوه استفاده از Rewire را در کنار Mox خواهیم دید.

بیا شروع کنیم!

مقدمه ای بر Rewire

یکی از چالش‌هایی که در مقاله قبلی با آن مواجه بودیم، نبود روشی ساختاریافته برای تعریف و تزریق وابستگی‌ها به ماژول‌هایمان بود. ما مجبور شدیم به صورت دستی مدل های خود را برای آزمایش تعریف کنیم.

اینجاست که Rewire و Mox وارد بازی می شوند:

  • سیم کشی مجدد روشی ساختارمندتر و انعطاف پذیرتر برای پیاده سازی DI در پروژه های اکسیر ارائه می دهد.
  • موکس کتابخانه ای است که به ما امکان می دهد برای تست های خود ماک تعریف کنیم.

ترکیب این دو ابزار می تواند به طور قابل توجهی تست پذیری و مدولار بودن برنامه های Elixir ما را بهبود بخشد.

بیایید با راه اندازی یک پروژه نمونه که از هر دو کتابخانه استفاده می کند، شروع کنیم.

چرا از Rewire و Mox برای Elixir استفاده کنیم؟

برای جمع‌بندی قسمت اول این مجموعه، مزایای DI برای آزمایش‌پذیری و مدولار بودن را مورد بحث قرار دادیم. دیدیم که چگونه می‌توانیم از وابستگی‌های عبور از طریق پارامترهای تابع استفاده کنیم:

  • ما … را داریم EmailScanner ماژولی که بر a SpamFilterService برای بررسی اینکه آیا ایمیل اسپم است یا خیر:
defmodule EmailScanner do
  def scan_email(spam_filter_service, email) do
    spam_filter_service.check_spam(email)
  end
end
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

  • ما … را داریم SpamFilterService ماژولی که منطق بررسی هرزنامه را پیاده سازی می کند:
defmodule SpamFilterService do
  def check_spam(email_content) do
    String.contains?(email_content, "spam")
  end
end
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

  • ما همچنین یک MockSpamFilterService ماژولی که اجرا می کند SpamFilterService رفتار برای اهداف آزمایشی:
defmodule MockSpamFilterService do
  def check_spam(_email), do: false
end
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

  • در نهایت، ما یک تست داریم که از آن استفاده می کند MockSpamFilterService برای تست کردن EmailScanner مدول:
defmodule EmailScannerTest do
  use ExUnit.Case

  test "scan_email with non-spam email returns false" do
    non_spam_email = %Email{content: "Hello, world!"}
    assert false == EmailScanner.scan_email(MockSpamFilterService, non_spam_email)
  end
end
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در Elixir، ماژول ها بدون حالت هستند، بنابراین راه اصلی برای انتقال وابستگی ها به یک ماژول از طریق پارامترهای تابع است. در حالی که ماژول‌های Elixir می‌توانند ویژگی‌هایی داشته باشند، اما برای اطلاعات و ابرداده‌های زمان کامپایل استفاده می‌شوند، نه برای نگهداری حالت زمان اجرا.

را بگیرید EmailScanner به عنوان مثال ماژول باید بگذریم SpamFilterService به عنوان پارامتری برای scan_email تابع. این غیرضروری است، زیرا تنها دلیل داشتن این پارامتر تابع، آزمایش پذیر ساختن ماژول است.

علاوه بر این، چند مشکل با خوانایی کد و ناوبری ایجاد می کند:

  • زیرا ماژول در انتظار است SpamFilterService به عنوان یک پارامتر، ما نمی توانیم به راحتی ببینیم که ماژول به چه چیزی بستگی دارد.
  • کامپایلر نمی‌تواند مشکلات مربوط به اجرای ماژول را پیدا کند، زیرا ما می‌توانیم هر ماژولی را که آن را پیاده‌سازی می‌کند، پاس کنیم SpamFilterService رفتار – اخلاق.

این رویکرد ممکن است برای پروژه های کوچک به خوبی کار کند، اما با رشد پروژه ما، ممکن است متوجه شویم که همان الگو را بارها و بارها تکرار می کنیم. با Rewire، ما نباید نگران این مسائل باشیم. ما فقط می‌توانیم روی نوشتن کدهای تمیز و قابل نگهداری تمرکز کنیم و در عین حال نگرانی‌های مربوط به آزمایش، تمسخرها و موارد خرد را در فایل‌های آزمایشی خود حفظ کنیم.

شروع کار با Rewire و Mox در پروژه اکسیر شما

بیایید اکنون به استفاده از Rewire و Mox در عمل بپردازیم.

مرحله 1: یک پروژه اکسیر جدید ایجاد کنید

قبل از ترکیب Rewire و Mox، یک پروژه Elixir جدید ایجاد کنید:

mix new email_scanner
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

این دستور یک پروژه Elixir جدید به نام تولید می کند email_scanner، از جمله ساختار درخت نظارت.

مرحله 2: افزودن وابستگی ها

برای استفاده از Rewire و Mox، باید آنها را به وابستگی های پروژه خود اضافه کنید. خود را به روز کنید mix.exs به صورت زیر فایل کنید:

defp deps do
  [
    {:rewire, "~> 1.0", only: :test},
    {:mox, "~> 1.0", only: :test}
  ]
end
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

پس از به روز رسانی وابستگی ها، اجرا کنید mix deps.get در ترمینال خود برای واکشی و نصب آنها.

بعد، اجازه دهید دو ماژول اصلی خود را تعریف کنیم:

defmodule EmailScanner do
  def filter_email(email) do
    email
    |> mark_as_important()
    |> SpamFilterService.check_spam()
  end

  defp mark_as_important(email) do
    important_senders = ["boss@example.com", "hr@example.com"]

    updated_email =
      if Enum.any?(important_senders, fn sender -> sender == email.sender end) do
        %{email | important: true}
      else
        email
      end

    updated_email
  end
end
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

ما مثال کد خود را کمی واقعی تر می کنیم. این filter_email تابع ایمیل‌های ارسال‌کننده‌های مهم را به‌عنوان مهم علامت‌گذاری می‌کند و بررسی می‌کند که ایمیل‌ها با استفاده از هرزنامه هستند یا خیر SpamFilterService مدول.

در مرحله بعد، ما را تعریف می کنیم SpamFilterService مدول:

defmodule SpamFilterService do
  def check_spam(email_content) do
    String.contains?(email_content, "spam")
  end
end
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بیایید یک تست اساسی برای آن ایجاد کنیم EmailScanner مدول:

defmodule EmailScannerTest do
  use ExUnit.Case

  describe "filter_email/2" do
    test "marks email as important from specific sender and checks for spam" do
      important_sender_email = %{sender: "boss@example.com", content: "Please review the attached report.", important: false}
      non_important_sender_email = %{sender: "random@example.com", content: "Check out these deals!", important: false}

      # Filtering emails sent from the important sender
      assert %{important: true, is_spam: false} = EmailScanner.filter_email(important_sender_email)

      # Filtering emails sent from a non-important sender
      assert %{important: false, is_spam: false} = EmailScanner.filter_email(non_important_sender_email)
    end
  end
end
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

در کد بالا، EmailScanner ماژول متکی بر SpamFilterService ماژول برای بررسی اینکه آیا یک ایمیل اسپم است یا خیر. با این حال، ما نمی توانیم آن را آزمایش کنیم EmailScanner ماژول بدون آزمایش SpamFilterService ماژول، که ایده آل نیست.

باید مسخره کنیم SpamFilterService ماژول تا بتوانیم آن را تست کنیم EmailScanner ماژول به صورت مجزا

مرحله 3: پیکربندی Mox

Mox به مقداری تنظیمات در پیکربندی آزمایشی شما نیاز دارد. a را باز کنید یا ایجاد کنید test/test_helper.exs فایل کنید و خط زیر را اضافه کنید تا یک مدل ساختگی بر اساس پروتکل یا رفتاری که پروژه شما استفاده می کند تعریف کنید:

ExUnit.start()
Mox.defmock(EmailScanner.SpamFilterServiceMock, for: EmailScanner.SpamFilterService)
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

Mox ساخت مسخره‌های مبتنی بر رفتارها یا پروتکل‌ها را برای ما آسان می‌کند، که برای آزمایش ماژول‌هایی که بر این انتزاع‌ها متکی هستند ضروری است.

زمانی که ماک ما تعریف شد، می‌توانیم به جای پیاده‌سازی واقعی از آن در تست‌های خود استفاده کنیم. با Rewire، می‌توانیم این مدل‌ها را بدون تکیه بر پارامترهای تابع به ماژول‌های خود تزریق کنیم.

مفاهیم اصلی Rewire

Rewire با ارائه یک رویکرد مبتنی بر کلان برای تعریف و تزریق وابستگی ها، فرآیند DI را در Elixir ساده می کند. این یکپارچه در اکوسیستم اکسیر قرار می گیرد و کدهای تمیز و قابل نگهداری را ترویج می کند.

تزریق وابستگی با Rewire در EmailScanner مدول

بیایید پیاده سازی کنیم EmailScanner ماژول، که متکی بر a SpamFilterService برای بررسی اینکه آیا یک ایمیل اسپم است یا خیر. با استفاده از Rewire به راحتی می توانیم این وابستگی را تزریق کنیم. به کد زیر دقت کنید:

defmodule EmailScanner do
  def filter_email(email) do
    email
    |> mark_as_important()
    |> SpamFilterService.check_spam()
  end

  defp mark_as_important(email) do
    important_senders = ["boss@example.com", "hr@example.com"]

    updated_email =
      if Enum.any?(important_senders, fn sender -> sender == email.sender end) do
        %{email | important: true}
      else
        email
      end

    updated_email
  end
end
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

تمسخر با موکس برای تست

برای تست کردن EmailScanner تابع فیلتر، ما می توانیم از Mox برای تمسخر استفاده کنیم SpamFilterService مدول:

defmodule EmailScannerTest do
  use ExUnit.Case

  import Rewire
  import Mox

  rewire EmailScanner, SpamFilterService: SpamFilterServiceMock

  # Ensure mocks are verified after each test
  setup :verify_on_exit!

  describe "filter_email/2" do
    test "marks email as important from specific sender and checks for spam" do
      important_sender_email = %{sender: "boss@example.com", content: "Please review the attached report.", important: false}
      non_important_sender_email = %{sender: "random@example.com", content: "Check out these deals!", important: false}

      # Stub the SpamFilter service to return false for all emails
      stub(SpamFilterServiceMock, :check_spam, fn _email -> :false end)

      # Filtering emails sent from the important sender
      assert %{important: true, is_spam: false} = EmailScanner.filter_email(important_sender_email)

      # Filtering emails sent from a non-important sender
      assert %{important: false, is_spam: false} = EmailScanner.filter_email(non_important_sender_email)
    end
  end
end
وارد حالت تمام صفحه شوید

از حالت تمام صفحه خارج شوید

بیایید آنچه را که در تست بالا اتفاق می افتد تجزیه کنیم:

در زیر کاپوت، Rewire در حال انجام چند کار جالب است. اول، درک فلسفه پشت Rewire و رویکردی که نویسنده تصمیم گرفته است را درک کنید. rewire با استفاده از ماکروها برای ایجاد یک کپی از ماژول کار می کند. بنابراین، برای هر آزمایش، Rewire یک ماژول جدید با خردهای مشخص شده ایجاد می کند.

ایجاد یک کپی از هر ماژول به جای نادیده گرفتن ماژول اصلی به ما این امکان را می دهد که آزمایش ها را به صورت موازی بدون هیچ گونه عوارض جانبی اجرا کنیم.

مواردی که هنگام استفاده از Rewire و Mox باید در نظر بگیرید

هنگام استفاده از Rewire و Mox در پروژه های Elixir خود، موارد زیر را در نظر بگیرید:

  • سازگاری تست ناهمزمان:
    Rewire به طور کامل از تست ناهمزمان با async: true. برخلاف نادیده گرفتن جهانی که توسط ابزارهایی مانند Meck استفاده می شود، Rewire یک کپی ماژول جداگانه برای هر آزمون ایجاد می کند. این تضمین می کند که تست ها می توانند به صورت موازی بدون تداخل با یکدیگر اجرا شوند.
  • ادغام با Mox:
    Rewire با تمرکز بر تزریق وابستگی بدون دیکته کردن منبع ماژول ساختگی، Mox را کاملاً تکمیل می کند. این هم افزایی امکان ادغام کارآمد و بدون درز بین این دو را فراهم می کند و آنها را به یک جفت عالی برای آزمایش اکسیر تبدیل می کند.
  • تاثیر بر سرعت تست:
    سیم‌کشی مجدد ممکن است کمی سرعت تست‌های شما را کاهش دهد، اگرچه تأثیر آن معمولاً حداقل است. داده های عملکرد جامع از پایگاه های کد بزرگ هنوز در انتظار است.
  • دقت پوشش تست:
    بله، پوشش آزمایشی به طور دقیق با Rewire گزارش می‌شود و اطمینان می‌دهد که می‌توانید به معیارهای پوشش آزمایشی خود اعتماد کنید.
  • سازگاری با فرآیندهای Stateful:
    Rewire با فرآیندهای حالت دار به خوبی کار می کند، مشروط بر اینکه این فرآیندها پس از سیم کشی مجدد ماژول آنها شروع شوند. برای فرآیندهایی که از قبل شروع شده اند (مانند کنترلرهای Phoenix)، Rewire ممکن است موثر نباشد زیرا سیم کشی مجدد دیگر نمی تواند اعمال شود. توصیه می شود از Rewire در درجه اول برای تست های واحدی که این محدودیت اعمال نمی شود، استفاده کنید.
  • سیم کشی مجدد ماژول Erlang:
    Rewire نمی تواند مستقیماً ماژول های Erlang را دوباره سیم کشی کند. با این حال، اجازه می دهد تا مراجع ماژول Erlang در ماژول های Elixir جایگزین شوند و راه حلی برای این محدودیت ارائه می دهد.
  • مدیریت ماژول های تو در تو:
    سیم‌کشی مجدد تنها وابستگی‌ها را در ماژول سیم‌کشی مجدد خاص جایگزین می‌کند. ماژول های اطراف یا تو در تو بدون تاثیر باقی می مانند و ارجاع به ماژول های اصلی را حفظ می کنند. برای کنترل کامل، ممکن است لازم باشد این ماژول ها را به صورت جداگانه سیم کشی کنید.
  • پیکربندی Formatter برای Rewire:
    برای جلوگیری از افزودن پرانتز در اطراف Rewire، فرمت ترکیبی را به‌روزرسانی کنید .formatter.exs فایل با import_deps: [:rewire]. این تضمین می کند که نحو Rewire به درستی بدون پرانتز غیر ضروری قالب بندی شده است.

و بس!

بسته بندی

در این پست بررسی کرده ایم که چگونه Rewire و Mox می توانند به تزریق وابستگی در Elixir کمک کنند.

استفان بهنکه، خالق Rewire، با تمایل به یک راه حل ظریف تر برای تزریق وابستگی در اکسیر، به ویژه برای آزمایش واحد، انگیزه داشت. من معتقدم که او موفق شد ابزاری عالی برای جامعه اکسیر فراهم کند.

همانطور که گفته شد، Rewire یک گلوله نقره ای نیست و ممکن است ابزار مناسبی برای هر پروژه ای نباشد. ارزیابی Rewire در کنار ابزارهایی مانند Meck و تصمیم گیری بر اساس پروژه و نیازهای تیم مهم است.

کد نویسی مبارک!

PS اگر می‌خواهید پست‌های اکسیر کیمیاگری را به محض انتشار آن‌ها بخوانید، در خبرنامه اکسیر کیمیاگری مشترک شوید و هیچ پستی را از دست ندهید!

نوشته های مشابه

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

دکمه بازگشت به بالا