রুবি/রেইলস ডেভেলপার হিসেবে, আমরা টেস্ট লিখতে ভালোবাসি। টেস্টিং সফটওয়্যার ডেভেলপমেন্টের একটি অপরিহার্য অংশ। ভালো টেস্ট আমাদের হাই-কোয়ালিটি কোড লিখতে সাহায্য করে। সেগুলো সামগ্রিক ডেভেলপমেন্ট প্রসেসে ভ্যালু যোগ করে, কিন্তু যদি আমরা টেস্ট ভালোভাবে ম্যানেজ না করি, সেগুলো আমাদের স্লো ডাউন করতে পারে। নিচে যেসব লক্ষণ থাকলে বুঝবেন টেস্ট ইমপ্রোপারলি ম্যানেজ হচ্ছে:
- টেস্ট চালানোর দীর্ঘ সময় লাগে।
- টেস্টগুলো অনরিলায়েবল এবং র্যান্ডমলি ফেইল করে।
- টেস্টগুলো ভিন্ন মেশিনে ভিন্ন বিহেভ করে।
- সকল টেস্ট স্যুট চালালে CI স্লো হয়।
এই ব্লগ পোস্টে, আমরা কভার করব কন্টেইনার কীভাবে ব্যবহার করা যায় টেস্টিং विश्वস্ত করতে, যা হাই-কোয়ালিটি সফটওয়্যার ডেলিভারি-তে সহায়ক।
ইন্টিগ্রেশন টেস্ট এবং ইউনিট টেস্ট
কন্টেইনার টপিকের মধ্যে ঝাঁপিয়ে পড়ার আগে, আসুন প্রথমে বুঝি এবং এক পেজে থাকি ইউনিট এবং ইন্টিগ্রেশন টেস্টের পার্থক্য নিয়ে।
ইউনিট টেস্ট হলো একটি কোড ব্লক আইসোলেশনে টেস্ট করার পদ্ধতি। সেগুলো কোড ব্লকের ফাংশনালিটি টেস্ট করতে সাহায্য করে কিন্তু ডিপেন্ডেন্সি নয়। উদাহরণস্বরূপ, এখানে একটি সিম্পল মেথড:
def sum(a, b) a + b end এটি দুটি সংখ্যা যোগ করার একটি ফাংশন, যা খুব সিম্পল। এই ফাংশন টেস্ট করা সুপার ইজি। কিন্তু যদি এই ফাংশনের একটি এক্সটার্নাল সার্ভিসে ডিপেন্ডেন্সি থাকে। এই মেথডকে ডেটাবেসে লিখতে হবে একবার যোগফল কমপ্লিট হলে।
def sum!(a, b) c = a + b Sum.create(value: c) end এই কোড ব্লকে, মানগুলো যোগ হয় এবং ডেটাবেসে সেভ হয়। এই কোড ব্লক টেস্ট করতে, আমাদের একটি ডেটাবেস সার্ভার রানিং থাকতে হবে। যদি আমরা ডেটাবেস সার্ভার রানিং থাকা অবস্থায় টেস্ট করি, এটা আর ইউনিট টেস্ট নয়। এটিকে এক্সটার্নাল ডিপেন্ডেন্সি দরকার, এবং আমরা ডেটাবেস কানেকশন এবং রেকর্ড ডেটাবেসে সেভ হচ্ছে কিনা সেটিও টেস্ট করব। এই কেসে, এই টেস্ট হলো একটি ইন্টিগ্রেশন টেস্ট।
ইউনিট টেস্ট হতে পারে সোলিটারি ইউনিট টেস্ট (সব ডিপেন্ডেন্সি মক/স্টাব) বা সোসিয়েবল ইউনিট টেস্ট (অন্য ডিপেন্ডেন্সির সাথে কথা বলতে দেয়)। লোকেরা ইউনিট টেস্টের বিভিন্ন ডেফিনিশন প্রপোজ করেছে। এই ব্লগ কনটেক্সটে, যে টাইপের ইউনিট টেস্টের কথা বলছি সেটি হলো সোলিটারি ইউনিট টেস্ট।
মক দিয়ে এক্সটার্নাল ডিপেন্ডেন্সি রিমুভ করা টেস্ট দ্রুত চালাতে সাহায্য করে। ইউনিট টেস্ট ব্যবহার করার সময়, আমরা এই ডিপেন্ডেন্সি বাদ দিই, কিন্তু অ্যাপ্লিকেশন প্রত্যাশা অনুযায়ী কাজ করছে কিনা নিশ্চিত করতে, ডিপেন্ডেন্সি টেস্ট করা সমানভাবে গুরুত্বপূর্ণ। যে টেস্টগুলো আমরা লিখি এই এক্সটার্নাল ডিপেন্ডেন্সি টেস্ট করতে, যেমন ডেটাবেস, নেটওয়ার্ক কল, এবং ফাইলস, সেগুলো হলো ইন্টিগ্রেশন টেস্ট।
ইউনিট টেস্টের মত, লোকেরা ইন্টিগ্রেশন টেস্টের 다양한 ডেফিনিশন প্রপোজ করেছে। তবে, বেসিক কনসেপ্ট একই। পার্থক্য শুধু স্কোপে। একটি ছোট ফাংশন যা ডেটাবেসে লিখে সেটি একটি ইন্টিগ্রেশন টেস্ট হতে পারে। একটি ব্রডার ফাংশনালিটি যা সিস্টেমের একাধিক পার্ট কানেক্ট করে সেটিও একটি ইন্টিগ্রেশন টেস্ট। আমরা যে টেস্টের কথা বলছি এখানে সেটি ন্যারো পার্টে বেশি ফোকাসড। টেস্ট যত ন্যারো, তত বেশি টেস্ট লিখতে হয়। ব্রডার টেস্টের কেসে, তবে, টেস্টের সংখ্যা কমে।
ইন্টিগ্রেশন টেস্ট চালানোর সময়, আমাদের অ্যাপ্লিকেশন এবং ডিপেন্ডেন্সি, যেমন ডেটাবেস এবং এক্সটার্নাল সার্ভিস, রানিং থাকতে হবে। এই ডিপেন্ডেন্সি ম্যানেজ করা চ্যালেঞ্জিং হতে পারে।
ডকারের ফান্ডামেন্টাল
Docker একটি টেকনোলজি যা একটি অ্যাপ্লিকेशनকে এর এনভায়রনমেন্ট (অপারেটিং সিস্টেম) থেকে অ্যাবস্ট্রাক্ট করতে সাহায্য করে এবং হোস্ট থেকে আইসোলেশনে রান করতে দেয়। এটি ভার্চুয়াল মেশিনের মত কিন্তু দ্রুত এবং বেশি এফিশিয়েন্ট। Docker আমাদের অ্যাপ্লিকেশন কোড এবং এর ডিপেন্ডেন্সি একটি সিঙ্গল কন্টেইনারে প্যাকেজ করতে দেয়। এটি কোডকে পোর্টেবল করে দেয় এবং মানে আমরা একই Docker ইমেজ ডেভেলপমেন্ট, টেস্টিং, এবং প্রোডাকশনে রান করতে পারি। এটি নিশ্চিত করবে অ্যাপ্লিকেশন রান হওয়ার এনভায়রনমেন্টের কনসিস্টেন্সি। যেহেতু এটি হোস্ট সার্ভারে ডিপেন্ডেন্ট নয়, এটি অ্যাপ্লিকেশনের বিহেভিয়র সব এনভায়রনমেন্টে প্রিডিক্টেবল করে। Docker ডেভেলপার এবং সিসঅপস দুজনের জন্যই জীবন সহজ করে, তাই DevOps কালচারে এটি খুব জনপ্রিয়।
আসুন কিছু গুরুত্বপূর্ণ টার্মিনোলজি বুঝি:
Dockerfile - একটি Dockerfile সিম্পলি একটি ফাইল যেখানে আমরা অ্যাপ্লিকেশনের জন্য প্রয়োজনীয় ডিপেন্ডেন্সি ডিফাইন করি। আমরা স펙িফাই করতে পারি অপারেটিং সিস্টেম কী লাগবে, ডেটাবেস ক্লায়েন্ট লাইব্রেরি, বা অ্যাপ্লিকেশনের জন্য প্রয়োজনীয় যেকোনো প্যাকেজ। এখানে, আমরা অ্যাপ্লিকেশন রান করার জন্য প্রয়োজনীয় কমান্ডও ডিফাইন করি। একটি Dockerfile একটি বেস ইমেজ থেকে শুরু হয়। এই বেস ইমেজগুলো হতে পারে একটি অপারেটিং সিস্টেম বা একটি প্রোগ্রামিং ল্যাঙ্গুয়েজ।
Images - ইমেজগুলো একটি ব্লুপ্রিন্ট যা স্টেট করে কন্টেইনার রান করার জন্য প্রয়োজনীয় স্টেপস। ইমেজগুলো বিভিন্ন লেয়ার দিয়ে বিল্ড হয়। Dockerfile-এ ডিফাইন করা প্রতিটি স্টেপ ইমেজের ভেতরে একটি লেয়ার। এটি রি-ইউজেবিলিটি বাড়ায় এবং ইন্টারমিডিয়েট লেয়ার ক্যাশ করতে হেল্প করে ইমেজ দ্রুত বিল্ড করতে।
Containers - কন্টেইনারেরা হলো একচুয় ইমেজ। সেগুলো স্টপড স্টেটেও থাকতে পারে। কন্টেইনারেরা একটি docker image থেকে স্টার্ট করা যেতে পারে। একটি সিঙ্গল docker image একাধিক docker কন্টেইনার স্টার্ট করতে পারে। কন্টেইনারেরা ইমেজ দ্বারা স펙িফাইড ইনফরমেশন ধারণ করে। একটি থিন কন্টেইনার লেয়ার থাকে যেখানে অ্যাপ্লিকেশন রান হওয়ার সময় ইমেজে করা সকল চেঞ্জ স্টোর হয়।
Docker Compose - Docker Compose একটি আলাদা টুল যা একাধিক কন্টেইনার ম্যানেজ করতে সাহায্য করে। আমরা একই docker image থেকে একাধিক কন্টেইনার পেতে পারি। আমাদের ভিন্ন docker image থেকে একাধিক কন্টেইনারও দরকার হতে পারে, এবং এই কন্টেইনারেরা একে অপরের সাথে ইন্টারঅ্যাক্ট করতে হবে। Docker Compose কন্টেইনারেরা ম্যানেজ করে। অন্যান্য টুলসও আছে, কিন্তু এই কনটেক্সটে, আমরা শুধু Docker Compose ব্যবহার করব এর সিম্পলিসিটির কারণে।
ভার্চুয়াল মেশিন হোস্ট হাইপারভাইজারের টপে থাকে এবং একটি আলাদা অপারেটিং সিস্টেম ইনস্টল করা লাগে, তাই বেশি হোস্ট রিসোর্স ইউজ হয়। তবে, Docker-এ, আমাদের শুধু হোস্ট অপারেটিং সিস্টেমে একটি Docker engine ইনস্টল করতে হয়, এবং হোস্ট রিসোর্স ম্যানেজমেন্ট Docker করে। Docker ডেভেলপার এবং সিসঅপস দুজনের জন্যই জীবন সহজ করে, তাই DevOps কালচারে এটি খুব জনপ্রিয়।
কেন ডকার টেস্টিং-জন্য
Docker রানিং অ্যাপ্লিকেশন বা টেস্টকে এর হোস্ট এনভায়রনমেন্ট থেকে আইসোলেট করে মিনিমাল রিসোর্স কনসাম্পশন দিয়ে। ডকার টেস্টিং-জন্য ব্যবহার করার কিছু সুবিধা:
লাইটওয়েট: Docker কন্টেইনারেরা লাইটওয়েট। এটি একসাথে একাধিক কন্টেইনার রান করতে সাহায্য করে এবং একটি টেস্ট প্রসেস অন্য টেস্ট প্রসেসে ইস্যু তৈরি করে না।
পোর্টেবল: Docker ইমেজগুলো পোর্টেবল এবং যেকোনো এনভায়রনমেন্টে রান করতে পারে। এটি CI-তে সাহায্য করে, কারণ আমরা একই ইমেজ রি-ইউজ করতে পারি লোকাল এবং CI এনভায়রনমেন্টে টেস্ট রান করতে।
ভার্সনিং: একাধিক অ্যাপ্লিকেশন ব্যবহার করা বা আমাদের ডিপেন্ডেন্সি আপগ্রেড করা আমাদের লোকাল মেশিনে আমাদের ডিপেন্ডেন্সির ভিন্ন ভার্সন দরকার করে। এটি ডকার ছাড়া খুব কঠিন ম্যানেজ করা। ডকারের সাথে, আমরা ভিন্ন ভার্সনে ভিন্ন কন্টেইনার রান করতে পারি। আমরা ভিন্ন ভার্সনে টেস্টও রান করতে পারি এবং ভেরিফাই করতে পারি।
মাইক্রোসার্ভিসের সহজ ম্যানেজমেন্ট: মাইক্রোসার্ভিসের সাথে, টেস্টিংও ডিস্ট্রিবিউটেড এবং জটিল হয়। এর মানে আমাদের ডিপেন্ডেন্ট মাইক্রোসার্ভিস ম্যানেজ করতে হবে। Docker এবং Docker Compose এই সার্ভিস ডিপেন্ডেন্সি ম্যানেজ করতে সাহায্য করে।
প্রেরিকোয়েজিট
এটি ট্রাই করার জন্য, নিচেরগুলো সেট আপ আছে কিনা নিশ্চিত করুন:
- Docker
- Docker-compose
- Ruby 2.7 (অপশনাল, কারণ আমরা রুবি ডকার কন্টেইনারে যোগ করব, তবে টেস্টিং-জন্য হেল্পফুল)
- Rails (5.1+ রিকমেন্ডেড)
অ্যাপ ডকারাইজ
আসুন একটি সিম্পল রেইলস অ্যাপ তৈরি করি এবং কন্টেইনারাইজ করি। আমি একটি ফ্রেশ রেইলস অ্যাপ থেকে শুরু করছি। আপনি বিদ্যমান রেইলস অ্যাপও কন্টেইনারাইজ করতে পারেন।
rails new calculator Dockerfile নামে একটি ফাইল তৈরি করুন নিচের কন্টেন্টসহ:
FROM ruby:2.7 RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list RUN apt-get update -qq && apt-get install -y nodejs yarn RUN mkdir /calculator WORKDIR /calculator COPY Gemfile /calculator/Gemfile COPY Gemfile.lock /calculator/Gemfile.lock RUN bundle install COPY package.json /calculator/package.json COPY yarn.lock /calculator/yarn.lock RUN yarn install --check-files COPY . /calculator EXPOSE 3000 CMD ["rails", "server", "-b", "0.0.0.0"] আমরা যে Dockerfile ডিফাইন করেছি সেটিতে Ruby 2.7 বেস ইমেজ আছে। আমাদের NodeJS এবং Yarn দরকার, তাই পরের ধাপ সেগুলো ইনস্টল করা। একবার সেগুলো ইনস্টল হলে, আমরা bundle install এবং yarn install রান করি। শেষে, আমরা সকল কোড কপি করি এবং ৩০০০ পোর্ট এক্সপোর্ট করে রেইলস সার্ভার রান করি। এটি একটি খুব সিম্পল Dockerfile। এর সাথে, আমাদের রেইলস অ্যাপ ডকারাইজড।
এখন আমরা ডকার ইমেজ তৈরি ও রান করতে পারি নিশ্চিত করতে সেটি রান হচ্ছে কিনা।
ডকার ইমেজ বিল্ড করুন:
docker build . -t calculator কন্টেইনার রান করুন:
docker run -p 3000:3000 calculator ক্যালকুলেশন লজিক সহ একটি মডেল তৈরী
আসুন calculation নামে একটি মডেল তৈরি করি যেখানে আমরা আমাদের ক্যালকুলেশন লজিক যোগ করব। আমরা এই মডেলটি ব্যবহার করব কিছু লজিক এবং সেগুলোর টেস্ট লিখতে। আমরা দেখব কীভাবে আমরা এই টেস্টের ডিপেন্ডেন্সি Docker Compose ব্যবহার করে ম্যানেজ করতে পারি।
আমরা বর্তমানে SQLite-র সাথে রেইলস রান করছি। আসুন এটিকে Postgres-এ চেঞ্জ করি SQLite-এর বদলে। এটি সহজে নিচের কমান্ড দিয়ে করা যায়:
rails db:system:change --to=postgresql একটি calculation মডেল জেনারেট করুন:
bundle exec rails generate model calculation calculation স্কিমাতে কিছু ফিল্ড যোগ করুন:
# db/migrate/create_calculation.rb class CreateCalculations < ActiveRecord::Migration[6.0] def change create_table :calculations do |t| t.integer :x t.integer :y t.integer :result t.timestamps end end end পরবর্তী, আসুন ডেটাবেস মাইগ্রেশন কন্টেইনারের ভেতরে রান করার চেষ্টা করি:
docker-compose exec web rails db:migrate যেহেতু ডেটাবেস কন্টেইনারের ভেতরে রান করছে না, এই কমান্ড রান করলে এরর থ্রো করবে।
rake aborted! PG::ConnectionBad: could not connect to server: No such file or directory Is the server running locally and accepting connections on Unix domain socket "/var/run/postgresql/.s.PGSQL.5432"? আমরা একটি Postgres ইমেজ অন্য একটি ইমেজ হিসেবে রান করতে পারি এবং তাদের মাঝে একটি নেটওয়ার্ক যোগ করতে পারি। এই কন্টেইনার কমিউনিকেশন ম্যানেজ করতে, আমরা Docker Compose ব্যবহার করতে পারি।
Docker Compose ব্যবহার করে একাধিক কন্টেইনার ম্যানেজ
রেইলস অ্যাপ কন্টেইনারাইজড হলে, আমরা সার্ভিসে ডিপেন্ডেন্সি যোগ করতে পারি Docker Compose ব্যবহার করে। অ্যাপ্লিকেশনের ডিপেন্ডেন্সি হলো Postgres।
Postgres Docker ইমেজ পাবলিকলি উপলব্ধ, তাই আমাদের একটা তৈরি করতে হয় না। তবে, আমাদের ডেটাবেস কন্টেইনার এবং আমাদের রেইলস অ্যাপ কন্টেইনার ম্যানেজ করতে হবে।
version: "3.0" services: db: image: postgres environment: POSTGRES_USER: calculator POSTGRES_PASSWORD: password POSTGRES_DB: calculator_test web: build: . ports: - "3000:3000" depends_on: - db environment: CALCULATOR_DATABASE_PASSWORD: password এখন, database.yaml ফাইলে যান এবং ডেটাবেসকে একটি ভিন্ন হোস্টে চেঞ্জ করুন। হোস্ট হবে Docker Compose ফাইলে সার্ভিসে স্পেসিফাইড নাম। আমাদের কেসে, হোস্ট হলো db। নিশ্চিত করুন যে DB পাসওয়ার্ড একই postgres সার্ভিস এবং web সার্ভিসে। এছাড়া ইউজারনেম, পাসওয়ার্ড, এবং ডেটাবেস নাম চেঞ্জ করুন Docker Compose ফাইলে স্পেসিফাইড অনুযায়ী।
# database.yaml development: <<: *default database: calculator_development username: calculator password: <%= ENV['CALCULATOR_DATABASE_PASSWORD'] %> এখন, আমরা একক কমান্ড দিয়ে কন্টেইনারগুলো স্টার্ট করতে পারি:
docker-compose up এটি ডেটাবেস এবং আমাদের রেইলস সার্ভার দুটোই স্টার্ট করবে। আমরা এখন মাইগ্রেশন কমান্ড রান করতে পারি, এবং সেটি পোস্টগ্রেসে কানেক্ট করে মাইগ্রেশন রান করতে পারবে।
টেস্ট যোগ করা
এখন যেহেতু সকল সেটআপ প্রস্তুত, আসুন মডেলের ভেতরে একটি সিম্পল মেথড লিখি এবং মেথডটি টেস্ট করি:
# calculation.rb class Calculation < ApplicationRecord def self.sum(x, y) result = x + y Calculation.create(x: x, y: y, result: result) result end end এই sum মেথড শুধু সংখ্যা যোগ করে না বরং সংখ্যাটি ডেটাবেসেও সেভ করে। তাই, এই মেথড টেস্ট করতে একটি ডেটাবেস ইনস্ট্যান্স রানিং থাকতে হবে।
এই টেস্ট হবে একটি ইন্টিগ্রেশন টেস্ট, কারণ আমাদের ডেটাবেসের সাথে কানেক্ট করতে হবে।
আমরা Mini-test ব্যবহার করব টেস্ট লিখতে, যা রেইলসের ডিফল্ট।
# calculation_test.rb require 'test_helper' class CalculationTest < ActiveSupport::TestCase test "should sum and save the data" do result = Calculation.sum(1, 2) c = Calculation.last assert result, 3 assert c.result, result end end টেস্ট রান করুন:
docker-compose exec web rails test উপরের টেস্টে, আমরা টেস্ট করছি sum মেথড মানগুলো যোগ করছে এবং ডেটাবেসে মানগুলো সেভ করছে কিনা। Docker Compose-এ, আমাদের ডেটাবেসের সাথে কানেক্ট করার খুব সহজ উপায় আছে।
এখানে, মেথড ডেটাবেসে ডিপেন্ডেন্ট। ডিপেন্ডেন্সি শুধু ডেটাবেস নয় বরং একটি থার্ড-পার্টি সার্ভিসও হতে পারে যা REST API প্রদান করে। তাহলে, আসুন একটি থার্ড-পার্টি সার্ভিস ব্যবহার করি যা একটি sum মেথড প্রদান করে, যেটি আমরা আমাদের নিজস্ব লিখার বদলে ব্যবহার করতে পারি।
# calculation.rb class Calculation < ApplicationRecord def self.sum(x, y) # হোস্টনেম একই হওয়া উচিত যেটি docker-compose ফাইলে স্পেসিফাইড url = 'https://sumservice:4010/sum' uri = URI(url) params = { :x => x, :y => y } uri.query = URI.encode_www_form(params) res = Net::HTTP.get_response(uri) throw "Error" unless res.is_a?(Net::HTTPSuccess) result = res.body.to_i Calculation.create(x: x, y: y, result: result) result end end আমরা ডেটাবেসের জন্য যা করেছি সেটির মত কিছু রেস্ট API-র জন্যও করতে পারি ডিপেন্ডেন্সি হিসেবে। আমাদের নিশ্চিত করতে হবে এগুলো বৈধ এন্ডপয়েন্ট, কিন্তু আমরা টেস্টের время আসল সার্ভিসে কল করব না। এই কেসে, আমরা মক/স্টাব টেস্ট তৈরি করতে পারতাম, কিন্তু যদি আমরা সেটি করি তবে এই টেস্ট ইন্টিগ্রেশন টেস্ট হবেনা। আমরা নিশ্চিত করতে চাই টেস্ট যতটা সম্ভব রিয়েলিস্টিক হয়। এই উদ্দেশ্যে, আমরা একটি মক API এন্ডপয়েন্ট তৈরি করতে পারি, যেটি আমরা একটি কন্টেইনারে রান করব, এবং মক কন্টেইনার API সার্ভিসে কল করব, যেটি আমাদের রেসপন্স দিবে রেজাল্টের সাথে।
টেস্ট প্রথম মডিফাই করি:
# calculation_test.rb require 'test_helper' class CalculationTest < ActiveSupport::TestCase test "should sum and save the data" do result = Calculation.sum(1, 2) c = Calculation.last // we don't need this assetion as we are deligation this responsibility to external service: # assert result, 3 // Not needed assert c.result, result end end এখানে, আমরা টেস্ট কেস মডিফাই করেছি শুধু টেস্ট করতে যে sum API দ্বারা প্রদত্ত রেজাল্ট ডেটাবেসে সঠিকভাবে সেভ হয়েছে কিনা। আমাদের sum-এর একচুয় ভ্যালু সঠিক কিনা টেস্ট করার দরকার নেই, কারণ এক্সটার্নাল সার্ভিস এটা হ্যান্ডেল করছে, এবং আমরা তাদের উপর বিশ্বাস করি।
একটি মক সার্ভার জেনারেট করতে, আমরা openAPI ব্যবহার করব। আসুন একটি সিম্পল স্বাগার ডেফিনিশন তৈরি করি:
রুট ডিরেক্টরিতে একটি নতুন ফোল্ডার তৈরি করুন এবং নাম দিন mock। ফোল্ডারের ভেতরে একটি ফাইল তৈরি করুন নাম api.yaml
# api.yaml swagger: "2.0" info: description: "This is a sum api provider" version: "1.0.0" title: "API Provider" host: "https://mock-service.com/" basePath: "/api" schemes: - "https" - "http" paths: /sum: get: produces: - "application/json" parameters: - name: "x" in: "query" description: "x value" required: true type: "integer" - name: "y" in: "query" description: "y value" required: true type: "integer" responses: "200": description: "successful operation" schema: type: "integer" example: 3 স্পেসিফিকেশন অনুযায়ী এই সার্ভার রান করতে, আমরা যেকোনো মক সার্ভিস প্রোভাইডার ব্যবহার করতে পারি। আমি prism নামক একটি টুল ব্যবহার করব। এর একটি docker ইমেজ উপলব্ধ, যেটি আমরা আমাদের docker-compose ফাইলে যোগ করতে পারি:
version: "3.0" services: db: image: postgres environment: POSTGRES_USER: calculator POSTGRES_PASSWORD: password POSTGRES_DB: calculator_test web: build: . ports: - "3000:3000" depends_on: - db - sumservice environment: CALCULATOR_DATABASE_PASSWORD: password sumservice: image: stoplight/prism:4 command: mock -h 0.0.0.0 "/tmp/api.yaml" volumes: - ./mock:/tmp/ ports: - 4010:4010 init: true এখন, টেস্ট রান করি এবং দেখি এটি এক্সপেক্টেডভাবে কাজ করছে কিনা:
> docker-compose exec web rails test Finished in 0.337710s, 2.9611 runs/s, 5.9222 assertions/s. 1 runs, 2 assertions, 0 failures, 0 errors, 0 skips Yeah! আমাদের এখন টেস্ট রানিং আছে। মেথড sum service Docker ইমেজের সাথে কানেক্ট করে sum পেতে এবং Postgres-এ কানেক্ট করে ডেটা সেভ করতে। সকল ডিপেন্ডেন্সি Docker Compose দ্বারা ম্যানেজড, তাই আমাদের পূর্বে ইনস্টল করা ডিপেন্ডেন্সি নিয়ে চিন্তা করতে হয় না। এই টেস্ট যেকোনো মেশিনে মুভ করলে কাজ করবে কোনো ডিপেন্ডেন্সি ইনস্টলেশন ছাড়া। একমাত্র জিনিস যা মেশিনে দরকার সেটি হলো docker।
যদি আপনার অ্যাপ্লিকেশন যেকোনো অন্য ডিপেন্ডেন্সির ওপর নির্ভর করে, যেমন Redis বা Memcached, সেগুলো dockerhub-এ পাওয়া যায়। আমরা আমাদের Docker Compose ফাইলে প্রয়োজন অনুযায়ী এই ইমেজগুলো যোগ করতে পারি।
প্যারালেল টেস্ট
Rails 5.1.5+ টেস্ট প্যারালেলে রান করা সাপোর্ট করে। ডিফল্টভাবে, টেস্টগুলো রেইলস ৬ থেকে প্রসেসরের সংখ্যা অনুযায়ী প্যারালেলে রান করে। টেস্ট রান করার সময়, এটি প্যারালেল টেস্টের সংখ্যা অনুযায়ী ডেটাবেস ইনস্ট্যান্স তৈরি করে। যদি আমাদের চারটি প্রসেসর থাকে, এটি ডেটাবেস "calculator_test-0", calculator_test-1, calculator_test-2, এবং calculator_test-3 তৈরি করে। আপনি পোস্টগ্রেস cli-এ যাই এবং ডেটাবেস চেক করলে নিচেরদিকের দেখবেন:
> \l List of databases Name | Owner | Encoding | Collate | Ctype | Access privileges -------------------+------------+----------+------------+------------+--------------------------- calculator_test | calculator | UTF8 | en_US.utf8 | en_US.utf8 | calculator_test-0 | calculator | UTF8 | en_US.utf8 | en_US.utf8 | calculator_test-1 | calculator | UTF8 | en_US.utf8 | en_US.utf8 | calculator_test-2 | calculator | UTF8 | en_US.utf8 | en_US.utf8 | calculator_test-3 | calculator | UTF8 | en_US.utf8 | en_US.utf8 | এখন, আপনি যদি রেইলস বা মিনি-টেস্ট বা রেইলসের পুরানো ভার্সন ব্যবহার করেন, আপনি এখনও ডকার দিয়ে টেস্ট প্যারালেলে রান করতে পারেন যদি আপনি সেগুলো ডকার দিয়ে লিখেন। রেইলস অটোমেটিকলি প্যারালেল টেস্টের জন্য ডেটাবেস তৈরি করে, কিন্তু যদি অন্য ডিপেন্ডেন্সি থাকে, যেমন Redis বা memcached, সেগুলো ম্যানুয়ালি ম্যানেজ করতে হতে পারে।
যেহেতু আমাদের সকল ডিপেন্ডেন্সি ম্যানেজড, আমরা Docker Compose ভিন্ন আর্গুমেন্টসহ রান করতে পারি টেস্ট প্যারালেলে রান করতে। এই উদ্দেশ্যে, আমরা স্পেসিফাই করতে পারি কোন ফোল্ডারগুলোতে টেস্ট রান করব প্যারালেলে, একাধিক Docker Compose ফাইল তৈরি করুন, এবং সেগুলো প্যারালেলে রান করুন।
Makefile যোগ করুন কমান্ড সিম্পলিফাই করতে
যেহেতু আমরা Docker এবং Docker Compose কমান্ড ব্যবহার করছি আমাদের টেস্ট রান করতে, কমান্ডগুলো লম্বা। সেগুলো মনে রাখতে এবং রান করতে ডে-টু-ডে ডেভেলপমেন্টের সময় কঠিন। এটি একটি সিম্পল কমান্ডে সিম্পলিফাই করতে, আসুন Makefile ব্যবহার করি। Makefile একটি সিম্পল টেক্সট ফাইল যা টার্গেট কমান্ড এক্সিকিউশন রেফারেন্স করে।
.PHONY: test up: docker-compose up down: docker-compose down test: docker-compose exec web rails test আমরা মেকফাইলে কিছু কমান্ড যোগ করেছি। এটিকে প্রয়োজন অনুযায়ী এক্সটেন্ড করা যায় অ্যাপ্লিকেশনের। মেকফাইলটি রেইলস অ্যাপ্লিকেশনের রুট ডিরেক্টরিতে যোগ করুন। একবার এই মেকফাইল হলে, আমরা সিম্পলি টেস্ট রান করতে পারি নিচের কমান্ড দিয়ে:
কন্টেইনার বুট করুন:
> make up টেস্ট রান করুন:
> make test লোকাল ডেভেলপমেন্ট-জন্য ডকার ব্যবহার
আমরা অ্যাপ্লিকেশন ডেভেলপ করার সময়ও Docker ব্যবহার করতে পারি। এটি নিশ্চিত করতে সাহায্য করে যে অ্যাপ্লিকেশন টেস্টিং, ডেভেলপমেন্ট, এবং প্রোডাকশন জুড়ে কনসিস্টেন্টলি বিহেভ করে। আসুন রিভিউ করি আমাদের কী চেঞ্জ করতে হবে উপরের কন্টেইনার লোকাল ডেভেলপমেন্ট-জন্য ব্যবহার করতে।
ভলিউম মাউন্ট
অ্যাপ্লিকেশন লোকালি রান করার সময়, আমরা চাই অ্যাপ্লিকেশন লোকাল সার্ভারে চেঞ্জ রিফ্লেক্ট করুক যখন আমরা কোডে চেঞ্জ করি। বর্তমান সিনেরিওতে, কোড আমাদের মেশিন থেকে ডকারে কপি হয় এবং সার্ভার স্টার্ট হয়। তাই, যদি আমরা লোকাল মেশিনে ফাইলে চেঞ্জ করি, সেগুলো ডকারে রিফ্লেক্ট হয় না। নিশ্চিত করতে যে কোড সবসময় সিঙ্কড থাকে, আমরা ভলিউম মাউন্ট ব্যবহার করতে পারি।
Docker Compose ফাইলে, আসুন ভলিউম যোগ করি:
version: "3.0" services: db: image: postgres environment: POSTGRES_USER: calculator POSTGRES_PASSWORD: password POSTGRES_DB: calculator_test web: build: . volumes: - .:/calculator # Volume mounted ports: - "3000:3000" depends_on: - db - sumservice environment: CALCULATOR_DATABASE_PASSWORD: password sumservice: image: stoplight/prism:4 command: mock -h 0.0.0.0 "/tmp/api.yaml" volumes: - ./mock:/tmp/ ports: - 4010:4010 init: true এখন, যদি আমরা সার্ভারে যেকোনো চেঞ্জ করি, চেঞ্জগুলো রিফ্লেক্ট হবে। আসুন এটি ট্রাই করি একটি কন্ট্রোলার তৈরি করে এবং আমাদের sum মেথড কল করে:
# controllers/calculation_controller.rb class CalculationController < ApplicationController def index result = Calculation.sum(params[:x], params[:y]) render json: {result: result} end end রুট রুট যোগ করুন:
# routes.rb Rails.application.routes.draw do # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html root "calculation#index" end এখন, যদি আমরা ব্রাউজারে অ্যাপ চেক করি, আমরা এক্সপেক্টেড রেসপন্স পাই:

লক্ষ্য করুন আমরা মক sumservice ব্যবহার করে সার্ভার স্টার্ট করছি, তাই URL-এ কুয়েরি প্যারামিটার হিসেবে যা দিই, এটি সবসময় ২ রিটার্ন করবে। এটি ডেভেলপমেন্টের জন্য ভালো পদ্ধতি না লাগতে পারে কিন্তু মক সার্ভিস প্রোভাইডার ব্যবহার করার তার নিজস্ব সুবিধা রয়েছে। মাইক্রোসার্ভিস ডেভেলপ করার সময়, অনেক ডিপেন্ডেন্ট সার্ভিস থাকতে পারে, যেগুলো বুট আপ করতে হবে সার্ভিস শুরু করার আগে। এটি ডেভেলপমেন্টের সময় প্রোডাক্টিভিটি বাড়ায়। আমরা অন্য সার্ভিসের সাথে চিন্তিত নই, শুধু যে সার্ভিস ডেভেলপ করছি সেটির সাথে।
যদি আমরা ফাইনাল ভেরিফিকেশন করতে চাই কীভাবে আসল সার্ভিসের সাথে কাজ করে, আমরা সেটি করতে পারি একটি নতুন Docker Compose ফাইল তৈরি করে এবং সেটিকে আসল সার্ভিসের ইমেজ দিয়ে রিপ্লেস করে। যদি এটি একটি ইন্টারনাল সার্ভিস হয়, আমরা সিম্পলি লেটেস্ট স্টেবল Docker ইমেজ দিয়ে রিপ্লেস করতে পারি। আমি ডেভেলপমেন্ট এনভায়রনমেন্টে ও মক সার্ভিস রাখার পরামর্শ দিচ্ছি।
.dockerignore
অ্যাপ্লিকেশন বড় হওয়ার সাথে সাথে, ডিপেন্ডেন্সি ডকার বিল্ড কনটেক্সটকে বড় করে দেবে। বড় বিল্ড কনটেক্সটের সাথে, ডকার ইমেজ বিল্ড করতে বেশি সময় লাগবে। ভলিউম মাউন্টও বিলম্ব করতে পারে অ্যাপ্লিকেশনের বড় সাইজের কারণে। এই কেসে, আমরা অনওয়ান্টেড ফাইল .dockerignore ফাইলে যোগ করতে পারি বিল্ড কনটেক্সট কমাতে।
.git* log/* README.md node_modules এন্ট্রিপয়েন্ট
যখন একটি রেইলস সার্ভার কন্টেইনারে রান করছে, আমরা কখনো একটি এরর মেসেজ পাই যে সার্ভার ইতিমধ্যে রান করছে কারণ PID ফাইল ডিলিট হয়নি কন্টেইনার এক্সিট করার সময়। এই এরর রিমুভ করতে, আমরা একটি ENTRYPOINT যোগ করতে পারি, যেটি server.pid ফাইল রিমুভ করবে।
FROM ruby:2.7 RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list RUN apt-get update -qq && apt-get install -y nodejs yarn RUN mkdir /calculator WORKDIR /calculator COPY Gemfile /calculator/Gemfile COPY Gemfile.lock /calculator/Gemfile.lock RUN bundle install COPY package.json /calculator/package.json COPY yarn.lock /calculator/yarn.lock RUN yarn install --check-files COPY . /calculator EXPOSE 3000 ENTRYPOINT ./entrypoint.sh CMD ["rails", "server", "-b", "0.0.0.0"] entrypoint.sh ফাইল তৈরি করুন:
#!/bin/bash set -e # Remove a potentially pre-existing server.pid for Rails. rm -f /calculator/tmp/pids/server.pid # Exec the container's main process (CMD command in rails file) exec "$@" এটিকে এক্সিকিউটেবল বানান
chmod +x entrypoint.sh এখন, যখন আমরা ডকার ইমেজ রান করব, আমরা এই ইস্যু ফেস করব না।
উপসংহার
ইন্টিগ্রেশন টেস্ট এবং ইউনিট টেস্ট আলাদা করা খুব গুরুত্বপূর্ণ। ইউনিট টেস্ট লিখার সময়, আমরা এক্সটার্নাল ডিপেন্ডেন্সি, যেমন ডেটাবেস, মক করতে পারি। এটি কোড আইসোলেশনে টেস্ট করতে সাহায্য করে। এটি সমানভাবে গুরুত্বপূর্ণ কোর বিজনেস লজিক এবং এর ডিপেন্ডেন্সির মাঝে ইন্টিগ্রেশন টেস্ট রান করা। ডিপেন্ডেন্সি, যেমন ডেটাবেস এবং এক্সটার্নাল সার্ভিস, ইন্টিগ্রেশন-এ টেস্ট করা যায় Docker এবং Docker Compose ব্যবহার করে। এই দুটো আলাদা রাখা ভালো প্র্যাকটিস। আমাদের সেগুলো আলাদা আলাদা রান করতে পারা উচিত। অধিক ইউনিট টেস্ট এবং কম ইন্টিগ্রেশন টেস্ট থাকলে আপনার টেস্ট দ্রুত এবং বেশি विश्वস্তভাবে রান হবে।
এই ওয়ার্কফ্লোর কিছু সুবিধা নিচে:
- ইউনিট এবং ইন্টিগ্রেশন টেস্টের মধ্যে সেগ্রিগেশন।
- যেহেতু সকল CI সার্ভিস কন্টেইনারে রান করে, লোকাল এবং CI-এ রান করা টেস্ট একই বিহেভিয়র থাকবে। এটি লোকাল মেশিন এবং CI সার্ভারে টেস্ট বিহেভিয়রের কনসিস্টেন্সি নিশ্চিত করতে সাহায্য করে।
- কন্টেইনার ব্যবহার করে অ্যাপ্লিকেশন ডিপ্লয় করা প্রোডাকশন সার্ভারে কনসিস্টেন্সি নিশ্চিত করতে সাহায্য করে।
- বিশ্বস্ত টেস্ট, কারণ ডিপেন্ডেন্সি ভালোভাবে ম্যানেজড।
- পোর্টেবল টেস্ট, একাধিক ডেভেলপার দ্বারা ব্যবহার করার সময় সেটআপে কম प्रयাস।