1. یونٹ ٹیسٹنگ کی تعارف

یونٹ ٹیسٹنگ سے مراد، پروگرام میں نہایت چھوٹی ٹیسٹیبل یونٹ کی جانچ پڑتال ہے، مثلاً گو لینگویج میں کسی فنکشن یا میتھڈ کی۔ یونٹ ٹیسٹنگ یہ یقینی بناتا ہے کہ کوڈ متوقع کام کر رہا ہے اور ڈویلپرز کو کوڈ میں تبدیلیاں کرنے کی اجازت دیتا ہے بغیر کے قدرتی طور پر موجودہ فعالیت کو توڑ دیں۔

جیو لینگو میں پراجیکٹ میں، یونٹ ٹیسٹنگ کی اہمیت کو کہنے کی ضرورت نہیں ہے۔ پہلے، یہ کوڈ کی معیار کو بہتر بنا سکتا ہے، جس سے ڈویلپرز کو کوڈ میں تبدیلیاں کرنے کی زیادہ یقینی ہوگی۔ دوسرا، یونٹ ٹیسٹنگ کوڈ کے لئے ترتیب کرنے کے تصور کو بیان کرتا ہے، جو کہاں کی متوقع رویہ ہے۔ اس کے علاوہ، یونٹ ٹیسٹس کو کنٹنیوس انٹیگریشن ماحول میں خود بخود چلانے سے نئے بگز وقت سے پوری طرح زیر صفحے کی جاسکتی ہیں، جو کہ سافٹ ویئر کی استحکام میں بہتری کا سبب بن سکتی ہے۔

2. گو لینگو کی ٹیسٹنگ پیکیج کا استعمال کرکے بنیادی ٹیسٹس کی ادائیگی

گو لینگو کی معیاری لائبریری میں ٹیسٹنگ پیکیج شامل ہے، جو ٹیسٹس لکھنے اور چلانے کے لئے آلات اور فنکشنلٹی فراہم کرتا ہے۔

2.1 اپنے پہلے ٹیسٹ کیس کی تخلیق

ٹیسٹ فنکشن لکھنے کے لئے، آپ کو _test.go سلفکس کے ساتھ فائل بنانی ہوگی۔ مثال کے طور پر، اگر آپ کا سورس کوڈ فائل calculator.go نام کا ہے، تو آپ کی ٹیسٹ فائل کا نام calculator_test.go ہوگا۔

اگلا کام ٹیسٹ فنکشن بنانے کا ہے۔ ایک ٹیسٹ فنکشن کو ٹیسٹینگ پیکیج کو امپورٹ کرنا ہوگا اور ایک معین فارمیٹ کو ماننا ہوگا۔ یہاں ایک سادہ مثال ہے:

// calculator_test.go
package calculator

import (
	"testing"
	"fmt"
)

// تجربہ تعین کرنا فنکشن جمع
func TestAdd(t *testing.T) {
	result := Add(1, 2)
	expected := 3

if result != expected {
		t.Errorf("متوقع %v تھا، لیکن حاصل %v ہے", expected, result)
	}
}

اس مثال میں، TestAdd ایک ٹیسٹ فنکشن ہے جو ایک خیالی Add فنکشن کا تجربہ کرتا ہے۔ اگر Add فنکشن کا نتیجہ متوقع نتیجہ کے مطابق ہے تو ٹیسٹ پاس ہو جائے گا؛ ورنہ، t.Errorf کو بلایا جائے گا کہ ٹیسٹ کے ناکام ہونے کے بارے میں معلومات ریکارڈ کرے۔

2.2 ٹیسٹ فنکشن کے نام باندیوں اور امضا کی سمجھ

ٹیسٹ فنکشن کے نام کا آغاز ٹیسٹ کے ساتھ، اس کے بعد کوئی غیر کم چھوٹی ہوئی سٹرنگ ہوگی، اور اس کا واحد پیرامیٹر ٹیسٹنگ.T کے پوائنٹر ہوگا۔ جیسا کہ مثال میں دکھایا گیا ہے، TestAdd صحیح نام باندیوں اور امضا کا پیروا کرتا ہے۔

2.3 ٹیسٹ کیسز کو چلانا

آپ اپنے ٹیسٹ کیسز کو کمانڈ-لاۓن ٹول کا استعمال کرکے چلا سکتے ہیں۔ ایک مخصوص ٹیسٹ کیس کے لئے، مندرجہ ذیل کمانڈ چلائیں:

go test -v // موجودہ ڈائریکٹری میں ٹیسٹ چلائیں اور تفصیلی خرجات دکھائیں

اگر آپ کوئی خاص ٹیسٹ کیس چلانا چاہتے ہیں، تو آپ -run پرچم کا استعمال کرسکتے ہیں اور اس کے بعد ریگولر ایکسپریشن دیں:

go test -v -run TestAdd // صرف TestAdd ٹیسٹ فنکشن کو چلائیں

go test کمانڈ خود بتائیں گی کہ کسی بھی _test.go فائل ملتی ہیں اور جو معیار کو مطمئن کرنے والے ہر ٹیسٹ فنکشن کو چلا دیں۔ اگر تمام ٹیسٹس پاس ہو جائیں، تو آپ کو کمانڈ لائن میں PASS جیسا پیغام دیکھائی دے گا۔ اگر کوئی بھی ٹیسٹ ناکام ہوجائے، تو آپ کو FAIL دکھائی دے گا ساتھ ہی ساتھ متعلقہ خرابی کی پیغام کے ساتھ۔

3. ٹیسٹ کیسز لکھنا

3.1 t.Errorf اور t.Fatalf کا استعمال کرکے خرابیوں کی رپورٹ

گو لینگو میں، ٹیسٹنگ فریم ورک نے خرابیاں رپورٹ کرنے کے لئے مختلف تراکیب فراہم کی ہیں۔ دو سب سے زیادہ استعمال ہونے والے فنکشنز Errorf اور Fatalf ہیں، دونوں ٹیسٹنگ.T آبجیکٹ کے میتھڈز ہیں۔ Errorf استعمال کیا جاتا ہے ٹیسٹ میں خرابیاں رپورٹ کرنے کے لئے لیکن موجودہ ٹیسٹ کیس کو روکتا نہیں ہے، جبکہ Fatalf خرابی رپورٹ کرنے کے بعد فوراً کوڈ کو روکتا ہے۔ مندرجہ ذیل مثال Errorf کا استخدام دکھاتی ہے:

func TestAdd(t *testing.T) {
    got := Add(1, 2)
    want := 3
    if got != want {
        t.Errorf("Add(1, 2) = %d; مواد ہے %d", got, want)
    }
}

اگر آپ چاہتے ہیں کہ جب خرابی پیش آئے تو ٹیسٹ فوراً روک دیا جائے، تو آپ Fatalf کا استعمال کرسکتے ہیں:

func TestSubtract(t *testing.T) {
    got := Subtract(5, 3)
    if got != 2 {
        t.Fatalf("Subtract(5, 3) = %d; مواد ہے 2", got)
    }
}

عموماً، اگر خرابی مندرجہ بالا کوڈ کو درست طور پر استعمال نہیں کرنے دے گی یا ٹیسٹ کی ناکامی پہلے سے ہی ثابت ہو چکی ہے، تو Fatalf کا استعمال کرنا مشورہ دیا جاتا ہے۔ ورنہ، زیادہ مکمل ٹیسٹ نتیجے حاصل کرنے کی خاطر Errorf کا استعمال مشورہ دیا جاتا ہے۔

3.2 ذیلی ٹیسٹس کی منظم کرنا اور ذیلی ٹیسٹس کی کارروائی

گو میں ہم t.Run کا استعمال کرتے ہیں تاکہ ہم ذیلی ٹیسٹس کو منظم کر سکیں، جو ہمیں ٹیسٹ کوڈ کو ایک زیادہ منظم طریقے سے لکھنے میں مدد دیتا ہے۔ ذیلی ٹیسٹس کا اپنا Setup اور Teardown ہوسکتا ہے اور انہیں الگ الگ چلایا جا سکتا ہے، جو بہترین لورز کا میدان ہے۔ یہ خاص طور پر جدید ٹیسٹس یا پیرامیٹرائزڈ ٹیسٹس کرنے کے لئے بہت کارآمد ہے۔

ذیلی ٹیسٹ t.Run کا استعمال کرنے کا مثال:

func TestMultiply(t *testing.T) {
    testcases := []struct {
        name           string
        a, b, expected int
    }{
        {"2x3", 2, 3, 6},
        {"-1x-1", -1, -1, 1},
        {"0x4", 0, 4, 0},
    }

    for _, tc := range testcases {
        t.Run(tc.name, func(t *testing.T) {
            if got := Multiply(tc.a, tc.b); got != tc.expected {
                t.Errorf("Multiply(%d, %d) = %d; چاہئے %d", tc.a, tc.b, got, tc.expected)
            }
        })
    }
}

اگر ہم "2x3" نام کا ذیلی ٹیسٹ الگ الگ چلانا چاہتے ہیں، تو ہم کمانڈ لائن میں مندرجہ ذیل کمانڈ چلا سکتے ہیں:

go test -run TestMultiply/2x3

براہ کرم نوٹ کریں کہ ذیلی ٹیسٹس کے نام معمول پر پیئر کیس سینسیٹو ہوتے ہیں۔

4. ٹیسٹنگ سے پہلے اور بعد کی تیاری

4.1 سیٹ اپ اور ٹیر ڈاؤن

جب ہم ٹیسٹس کر رہے ہوتے ہیں، تو ہمیں عام طور پر ٹیسٹس کے لئے کچھ ابتدائی حالات تیار کرنے کی ضرورت ہوتی ہے (جیسے ڈیٹا بیس کنیکشن، فائل کی تخلیق وغیرہ)، اور اسی طرح، ٹیسٹس مکمل ہونے کے بعد کچھ صفائی کا کام کرنا پڑتا ہے۔ گو میں، عام طور پر ہم ٹیسٹ فنکشن میں Setup اور Teardown کرتے ہیں، اور t.Cleanup فنکشن ہمیں صفائی کے کال بیک فنکشنز کو رجسٹر کرنے کی سہولت فراہم کرتا ہے۔

یہاں ایک سادہ مثال ہے:

func TestDatabase(t *testing.T) {
    db, err := SetupDatabase()
    if err != nil {
        t.Fatalf("setup failed: %v", err)
    }

    // ٹیسٹ مکمل ہونے پر یقینی بنانے کے لئے ایک صفایٹی بیک کال بیک فنکشن رجسٹر کریں
    t.Cleanup(func() {
        if err := db.Close(); err != nil {
            t.Errorf("failed to close database: %v", err)
        }
    })

    // ٹیسٹ کو انجام دیں...
}

فنکشن TestDatabase میں، ہم پہلے SetupDatabase فنکشن کو بلاتے ہیں تاکہ ٹیسٹ ماحول تیار کیا جائے۔ پھر ہم t.Cleanup() استعمال کرتے ہیں تاکہ ٹیسٹ مکمل ہونے کے بعد صفائی کا کام کرنے کے لئے ایک فنکشن رجسٹر کریں، اس مثال میں، یہ ہے ڈیٹا بیس کنیکشن بند کرنا۔ اس طرح، ہم یہ یقینی بنا سکتے ہیں کہ چاہے ٹیسٹ میں کامیاب ہو یا ناکامی،، وسائل صحیح طریقے سے رہائی حاصل ہوتی ہے۔

5. ٹیسٹ کارروائی کو بہتر بنانا

ٹیسٹ کارروائی کو بہتر بنانے سے ہمیں ترقی کے خطے پر تیزی سے ڈوبتے ہوئے، جلد مسائل کا پتہ لگانے اور کوڈ کی معیار کی یقینیت فراہم ہوتی ہے۔ نیچے، ہم ٹیسٹ کوریج، ٹیبل-ڈرائیون ٹیسٹس، اور ماکس کا استعمال کر کے ٹیسٹ کارروائی کو بہتر بنانے کے بارے میں گفتگو کریں گے۔

5.1 ٹیسٹ کوریج اور متعلق اوزار

go test ٹول ٹیسٹ کوریج کی ایک بہترین خصوصیت فراہم کرتا ہے، جو ہمیں سمجھنے میں مدد فراہم کرتا ہے کہ کوڈ کے کن حصوں کو ٹیسٹ کیس کور کیا گیا ہے، اس طرح کوڈ کے علاقے کو پتا کرنے کا مواقع حاصل ہوتا ہے جو کہ ٹیسٹ کیس کور نہیں ہیں۔

go test -cover کمانڈ کا استعمال کرتے ہوئے، آپ موجودہ ٹیسٹ کوریج فیصد دیکھ سکتے ہیں:

go test -cover

اگر آپ چاہتے ہیں کہ کون سے لائنز کوڈ کو استعمال کیا گیا اور کون سے نہیں، اس کی مکمل سمجھ پانے کیلئے، تو آپ -coverprofile پیرامیٹر کا استعمال کر سکتے ہیں، جو کہ ایک کوریج ڈیٹا فائل پیدا کرتا ہے۔ پھر آپ go tool cover کمانڈ کا استعمال کر کے ایک تفصیلی ٹیسٹ کوریج رپورٹ بنا سکتے ہیں۔

go test -coverprofile=coverage.out
go tool cover -html=coverage.out

مندرجہ بالا کمانڈ کو استعمال کرنے سے ایک ویب رپورٹ کھلے گا، جو کہ وضاحت سے دکھاتا ہے کہ کون سی لائنز کوڈ ٹیسٹس سے گزرا گیا ہے اور کون نہیں۔ سبز رنگ ٹیسٹس سے گزری ہوئی کوڈ کی نشان دہی کرتا ہے، جبکہ سرخ رنگ کوڈ کی نشان دہی کرتا ہے جو کہ ٹیسٹس سے گزرا نہیں گیا۔

5.2 موکس کا استعمال

ٹیسٹنگ میں ہم وہ سیٹویشنز کا سامنا کرتے ہیں جہاں ہمیں بیرونی تعلقات کی نقلی بنانے کی ضرورت ہوتی ہے۔ موکس ہمیں ان تعلقات کی نقلی بنانے میں مدد فراہم کر سکتے ہیں اور ٹیسٹنگ ماحول میں مخصوص بیرونی خدمات یا وسائل پر اعتماد کرنے کی ضرورت کو ختم کر سکتے ہیں۔

گولینگ کمیونٹی میں кафی موکس ٹولز ہیں، جیسے کے testify/mock اور gomock۔ یہ ٹولز عام طور پر موکس اشیاء بنانے اور استعمال کرنے کے لئے ایپلیکیشن پروگرامنگ انٹرفیس فراہم کرتے ہیں۔

یہاں testify/mock کا بنیادی مثال ہے۔ پہلا کام یہ ہے کہ ہمیں ایک انٹرفیس اور اس کا موکس ورژن تعین کرنا ہے۔

type DataService interface {
    FetchData() (int, error)
}

type MockDataService struct {
    mock.Mock
}

func (m *MockDataService) FetchData() (int, error) {
    args := m.Called()
    return args.Int(0), args.Error(1)
}

ٹیسٹنگ میں ہم MockDataService کا استعمال کر کے واقعی ڈیٹا سروس کی جگہ لے سکتے ہیں:

func TestSomething(t *testing.T) {
    mockDataSvc := new(MockDataService)
    mockDataSvc.On("FetchData").Return(42, nil) // متوقع رویہ کو تشکیل دینا

    result, err := mockDataSvc.FetchData() // موک اشیا کا استعمال
    assert.NoError(t, err)
    assert.Equal(t, 42, result)

    mockDataSvc.AssertExpectations(t) // جانچ پڑتال کرنا کہ کیا متوقع رویہ واقعی ہوا یا نہیں
}

اوپر دیئے گئے ترکیب کے ذریعے ہم ٹیسٹنگ میں بیرونی سروس، ڈیٹا بیس کالز وغیرہ پر اعتماد کو بچا سکتے ہیں۔ یہ ٹیسٹ کی اجراء فراری کو تیز کرسکتا ہے اور ہمارے ٹیسٹز کو مضبوط اور قابل اعتماد بنا سکتا ہے۔

6. ترقی یافتہ ٹیسٹنگ تکنیک

جیسے ہیم نے گولینگ یونٹ ٹیسٹنگ کی بنیادی باتیں سیکھ لی ہیں، ہمیں مزید ترقی یافتہ ٹیسٹنگ تکنیکس کا خوبصورت سیکھنے کا موقع ملتا ہے جو کہ مضبوط سوفٹویئر بنانے اور ٹیسٹنگ کی کارکردگی میں بہتری لاتے ہیں۔

6.1 نجی فنکشن کی ٹیسٹنگ

گولینگ میں نجی فنکشنز عموماً غیر ایکسپورٹ کردہ فنکشنز کو ظاہر کرتے ہیں، یعنی، ان فنکشنز کے نام چھوٹے حروف سے شروع ہوتے ہیں۔ عام طور پر ہم ان کے عوامی انٹرفیسز کی ٹیسٹنگ کو ترجیح دیتے ہیں، چونکہ یہ کوڈ کی استعمالیت کو عکس کرتے ہیں۔ لیکن کچھ صورتوں میں نجی فنکشنز کی سیدھی ٹیسٹنگ بھی منطقی ہوتی ہے، جیسے وقتی وقتی پرائیویٹ فنکشن میں پریشان کن منطق ہو اور یہ عموماً متعدد عوامی فنکشنز سے بلایا ہوتا ہے۔

نجی فنکشن کی ٹیسٹنگ عوامی فنکشن کی ٹیسٹنگ سے مختلف ہوتی ہے کیونکہ یہ پیکیج کے باہر سے قابل رسائی ممکن نہیں ہوتی ہیں۔ ایک عام تکنیک یہ ہے کہ ٹیسٹ کوڈ کو ایک ہی پیکیج میں لکھا جائے اور اس طرح پرائیویٹ فنکشن تک رسائی حاصل ہو سکے۔

یہاں ایک سادہ مثال ہے:

// calculator.go
package calculator

func add(a, b int) int {
    return a + b
}

مطابقتی ٹیسٹ فائل مندرجہ ذیل ہے:

// calculator_test.go
package calculator

import "testing"

func TestAdd(t *testing.T) {
    expected := 4
    actual := add(2, 2)
    if actual != expected {
        t.Errorf("متوقع %d، حاصل %d", expected, actual)
    }
}

پیکیج کو ایک ہی جگہ پر رکھنے کے ذریعے ہم add فنکشن کی سیدھی ٹیسٹ کر سکتے ہیں۔

6.2 عام ٹیسٹنگ دستاویز اور بہترین عمل

گولینگ کا یونٹ ٹیسٹنگ کئی عام پیٹرنز کو فراہم کرتا ہے جو ٹیسٹنگ کام کو آسان بناتے ہیں اور کوڈ کی وضاحت اور قابلیتِ حفاظت میں مدد فراہم کرتے ہیں۔

  1. ٹیبل-ڈریون ٹیسٹس

    ٹیبل-ڈریون ٹیسٹنگ ایک ترتیب دینے کا طریقہ ہے جو ٹیسٹ کے ان پٹس کو اور متوقع خروجات کو تعین کرتا ہے۔ ایک مجموعہ کے تعین پر پیش کرنے اور پھر انہیں ٹیسٹ کرنے کیلئے ان کو لوپ کرنے کے ذریعے، یہ طریقہ نئے ٹیسٹ کیسس کو شامل کرنا بہت آسان بناتا ہے اور کوڈ کو پڑھنے اور حفظ کرنا بھی آسان بناتا ہے۔

    // calculator_test.go
    package calculator

    import "testing"

    func TestAddTableDriven(t *testing.T) {
        var tests = []struct {
            a, b   int
            want   int
        }{
            {1, 2, 3},
            {2, 2, 4},
            {5, -1, 4},
        }

        for _, tt := range tests {
            testname := fmt.Sprintf("%d,%d", tt.a, tt.b)
            t.Run(testname, func(t *testing.T) {
                ans := add(tt.a, tt.b)
                if tt.want != ans {
                    t.Errorf(" حاصل %d، متوقع %d", ans, tt.want)
                }
            })
        }
    }
  1. ٹیسٹنگ کے لئے موکس کا استعمال

    موکنگ ایک ٹیسٹنگ تکنیک ہے جو فعالیت کے مختلف حصوں کو ٹیسٹ کرنے کے لئے تعلقات کی جگہ کو بدلنے کا شامل ہے۔ گولینگ میں، انٹرفیسیز موکس کو بنانے اور استعمال کرنے کا ذریعہ ہیں۔ انٹرفیسوں کا استعمال کرتے ہوئے، ایک موک امپلیمنٹیشن بنائی جا سکتی ہے اور پھر اس کو ٹیسٹ کرنے کیلئے استعمال کیا جا سکتا ہے۔