Not
Bu sayfaya erişim yetkilendirme gerektiriyor. Oturum açmayı veya dizinleri değiştirmeyi deneyebilirsiniz.
Bu sayfaya erişim yetkilendirme gerektiriyor. Dizinleri değiştirmeyi deneyebilirsiniz.
Bu makale, sürücü go-mssqldb kullanılırken SQL Server'a karşı entegrasyon testleri yazma kalıplarını ele alır.
Doğru test türünü seçin
Gerçek bir SQL Server örneğine karşı test yapmayı tercih ederim. Bir SQL Server konteyneri (Docker Compose veya bir CI servisi aracılığıylatestcontainers-go) SQL sözdizimi hatalarını, tür uyumsuzluklarını ve mock'ların tespit edemediği işlem davranışlarını yakalar. Bu yaklaşım, sürücünün go-mssqldb kendi test paketi için kullandığı yöntemdir. Windows'ta ise LocalDB, Docker gerektirmeyen hafif bir alternatiftir.
Sadece go-sqlmock hızlı iç döngü birim testleri için geri dönün, burada konteyner başlatma süresi yürütmeyi kontrol eder. Örneğin, uygulama katmanı yeniden deneme mantığı veya sonuç eşlemesini test etmek için kullanın.
| Test türü | Şunun için kullanın | Bundan kaçının: |
|---|---|---|
testcontainers-go ile entegrasyon testleri |
Paylaşılan altyapıyı yönetmeden gerçek bir SQL Server örneği gerektiren tekrarlanabilir CI çalışmaları ve test kümeleri. | Konteyner başlatma süresinin yürütmeye hakim olduğu hızlı iç döngü testleri. |
| Paylaşılan veya yerel SQL Server'a karşı entegrasyon testleri | Depolanan prosedürler, şema nesneleri, işlem davranışı, geçici tablolar ve uçtan uca sürücü davranışı. | Testler izole altyapıya ihtiyaç duyar veya CI'de harici bağımlılıklar olmadan tutarlı şekilde çalışmalıdır. |
go-sqlmock ile birim testleri |
SQL söz dizimini doğrulamanız gerekmediğinde, yeniden deneme döngüleri, sonuçların eşlenmesi ve hata durumlarına göre dallanma gibi uygulama katmanı mantığı | Sürücü davranışını, SQL sözdizimini SQL Server'a karşı doğrulamanız veya işlem semantiği doğrulamanız gerekir. |
Test veritabanı kurulumu
Entegrasyon testleri için test bağlantı dizesi'i yapılandırmak amacıyla ortam değişkenlerini kullanın. Bu yaklaşım, kimlik bilgilerini kaynak kodundan uzak tutar ve CI/CD entegrasyonunu kolaylaştırır:
package myapp_test
import (
"database/sql"
"os"
"testing"
_ "github.com/microsoft/go-mssqldb"
)
var testDB *sql.DB
func TestMain(m *testing.M) {
connString := os.Getenv("TEST_MSSQL_URL")
if connString == "" {
panic("TEST_MSSQL_URL is not set")
}
var err error
testDB, err = sql.Open("sqlserver", connString)
if err != nil {
panic("Failed to open test DB: " + err.Error())
}
defer testDB.Close()
if err = testDB.Ping(); err != nil {
panic("Failed to connect to test DB: " + err.Error())
}
os.Exit(m.Run())
}
Testleri çalıştırmadan önce ortam değişkenini ayarlayın:
export TEST_MSSQL_URL="sqlserver://<user>:<password>@<server>:1433?database=<database>&encrypt=true&TrustServerCertificate=true"
go test ./...
Test izolasyonu için işlemleri kullanın
Her testi bir işlem içine alın ve sonunda geri alın. Bu yaklaşım, testler arasında veritabanını temiz tutar:
func TestInsertDepartment(t *testing.T) {
tx, err := testDB.Begin()
if err != nil {
t.Fatal(err)
}
defer tx.Rollback() // Always roll back - never commits
_, err = tx.Exec(
"INSERT INTO HumanResources.Department (Name, GroupName) VALUES (@p1, @p2)",
sql.Named("p1", "TestDept"),
sql.Named("p2", "TestGroup"))
if err != nil {
t.Fatal(err)
}
var count int
err = tx.QueryRow("SELECT COUNT(*) FROM HumanResources.Department WHERE Name = @p1",
sql.Named("p1", "TestDept")).Scan(&count)
if err != nil {
t.Fatal(err)
}
if count != 1 {
t.Errorf("Expected 1 row, got %d", count)
}
}
Bu desen, tek bir işlem sınırı içinde depo kodunu kullanan testler için en iyi şekilde çalışır. Kendi işlemlerini dahili olarak başlatıp onaylayan kodlar veya birden fazla bağlantı genelinde davranışı doğrulaması gereken testler için pek uygun değildir.
CI/CD için Docker’da SQL Server
CI boru hatlarında entegrasyon testleri için SQL Server Linux konteyneri kullanın:
# GitHub Actions example
services:
mssql:
image: mcr.microsoft.com/mssql/server:2025-latest
env:
ACCEPT_EULA: "Y"
MSSQL_SA_PASSWORD: "<password>"
ports:
- 1433:1433
Sonra test bağlantı dizesi'i ayarlayın:
env:
TEST_MSSQL_URL: "sqlserver://sa:<password>@localhost:1433?database=AdventureWorks2025"
Veritabanı yokken testleri atlayın
SQL Server örneği her zaman erişilebilir olmayabilecek projelerde, entegrasyon testlerini zarifçe atlayın:
func TestQueryEmployees(t *testing.T) {
if os.Getenv("TEST_MSSQL_URL") == "" {
t.Skip("TEST_MSSQL_URL not set, skipping integration test")
}
// ... test body
}
Test yardımcısı: tablo oluşturma ve silme
Test tablosu oluşturan ve testten sonra temizleyen bir yardımcı fonksiyon oluşturun:
func withTestTable(t *testing.T, db *sql.DB, fn func()) {
t.Helper()
_, err := db.Exec(`
IF OBJECT_ID('dbo.TestItems', 'U') IS NOT NULL DROP TABLE dbo.TestItems;
CREATE TABLE dbo.TestItems (Id INT IDENTITY PRIMARY KEY, Name NVARCHAR(50));
`)
if err != nil {
t.Fatal("Setup failed:", err)
}
defer func() {
db.Exec("DROP TABLE IF EXISTS dbo.TestItems")
}()
fn()
}
go-sqlmock ile birim testleri
Eğer konteyner başlatma süresi iç geliştirme döngünüz için çok yavaşsa, go-sqlmock önceden tanımlanmış sonuçları geri getiren bir bellek *sql.DB içi oluşturur. SQL sözdizimini gerçek bir sunucuya karşı doğrulamanıza gerek kalmadığı uygulama katmanı mantığı için kullanın (tekrar deneme döngüleri, sonuç eşleme, hata dallanma):
go get github.com/DATA-DOG/go-sqlmock
Bir sorgunun benzerini oluşturun
Beklenen sorguları kurun ve uygulamanın sonuçları doğru şekilde ele aldığından emin olun:
Uyarı
sqlmock.ExpectQuery girdisini sıradan bir SQL dizisi değil, düzenli ifade olarak ele alır.
(, ), + ve . gibi karakterler, SQL metninde birebir eşleştirilebilmeleri için öncelemeli yazılmalıdır. Go dize literallerinde bu kaçışlar çift olarak görünür (örneğin, \\( regex'teki bir literal ( için).
package myapp_test
import (
"testing"
"github.com/DATA-DOG/go-sqlmock"
)
func TestGetEmployee(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatal(err)
}
defer db.Close()
rows := sqlmock.NewRows([]string{"BusinessEntityID", "Name", "Location"}).
AddRow(1, "Alice", "Canada")
mock.ExpectQuery("SELECT TOP \\(1\\) BusinessEntityID, FirstName \\+ ' ' \\+ LastName AS Name, CountryRegionName AS Location FROM Sales\\.vSalesPerson WHERE BusinessEntityID = @p1").
WithArgs(1).
WillReturnRows(rows)
emp, err := GetEmployee(db, 1)
if err != nil {
t.Fatal(err)
}
if emp.Name != "Alice" {
t.Errorf("Expected Alice, got %s", emp.Name)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("Unmet expectations: %v", err)
}
}
Bir hatayı simüle et
Hata işleme akışlarını test etmek için mock nesnesinden bir hata döndürün:
func TestGetEmployeeNotFound(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatal(err)
}
defer db.Close()
mock.ExpectQuery("SELECT").
WithArgs(999).
WillReturnError(sql.ErrNoRows)
_, err = GetEmployee(db, 999)
if err == nil {
t.Error("Expected error for nonexistent employee")
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("Unmet expectations: %v", err)
}
}
Tip
Veri erişim işlevlerinizi, paket düzeyinde genel bir değişken kullanmak yerine, parametre olarak *sql.DB'ı (veya bir arayüzü) kabul edecek şekilde tasarlayın. Bu desen, testlerde go-sqlmock veritabanlarının yerine kullanılmasını kolaylaştırır.
testcontainers-go ile entegrasyon testleri
testcontainers-go her test paketi için bir SQL Server kapsayıcısı oluşturur ve iş bitince onu otomatik olarak kaldırır. Bu yaklaşım, paylaşılan altyapıyı yönetmeden gerçek SQL Server davranışını doğruladığı için çoğu test paketi için önerilir:
go get github.com/testcontainers/testcontainers-go
go get github.com/testcontainers/testcontainers-go/modules/mssql
Bu örneği yerel olarak çalıştırmak için:
- Docker Desktop veya başka bir yerel Docker motorunun çalıştığından emin olun.
- Testi modülünüzdeki bir
_test.godosyaya kaydedin. - Modül kökünden çalıştırın
go test -run TestWithContainer -v ./....
Test paketinizin çoğunda bu yaklaşımı kullanın. Konteynerin başlatılması birkaç saniye sürer, ancak mock nesnelerinin gözden kaçırdığı sorunları yakalayan gerçek SQL Server doğrulamasını elde edersiniz.
package myapp_test
import (
"context"
"database/sql"
"testing"
_ "github.com/microsoft/go-mssqldb"
"github.com/testcontainers/testcontainers-go/modules/mssql"
)
func TestWithContainer(t *testing.T) {
ctx := context.Background()
container, err := mssql.Run(ctx,
"mcr.microsoft.com/mssql/server:2025-latest",
mssql.WithAcceptEULA(),
mssql.WithPassword("<password>"))
if err != nil {
t.Fatal(err)
}
defer container.Terminate(ctx)
connStr, err := container.ConnectionString(ctx)
if err != nil {
t.Fatal(err)
}
db, err := sql.Open("sqlserver", connStr)
if err != nil {
t.Fatal(err)
}
defer db.Close()
// Create schema.
_, err = db.ExecContext(ctx, `
CREATE TABLE dbo.TestDepartments (
Id INT IDENTITY PRIMARY KEY,
Name NVARCHAR(50),
GroupName NVARCHAR(50)
)`)
if err != nil {
t.Fatal(err)
}
// Run tests against the real database.
_, err = db.ExecContext(ctx,
"INSERT INTO dbo.TestDepartments (Name, GroupName) VALUES (@p1, @p2)",
sql.Named("p1", "Data Science"),
sql.Named("p2", "Research and Development"))
if err != nil {
t.Fatal(err)
}
var count int
err = db.QueryRowContext(ctx, "SELECT COUNT(*) FROM dbo.TestDepartments").Scan(&count)
if err != nil {
t.Fatal(err)
}
if count != 1 {
t.Errorf("Expected 1 row, got %d", count)
}
}
Testcontainers: x509 sertifika hatası
Go 1.23 ve sonrasında bir SQL Server konteynerine bağlanırken şu hatayı görebilirsiniz:
x509: negative serial number
Go 1.23, RFC 5280'i sıkı bir şekilde uygular ve Docker'da SQL Server tarafından oluşturulan kendi kendine imzalanmış sertifika negatif seri numarası kullanır. Test konteynerleri üretim seviyesinde TLS gerektirmediğinden, test bağlantı dizesi'e ekleyin TrustServerCertificate=true veya encrypt=disable ekleyin:
connStr, err := container.ConnectionString(ctx, "TrustServerCertificate=true")
if err != nil {
t.Fatal(err)
}
Dikkat
Sadece test ortamlarında kullanınTrustServerCertificate=true.encrypt=disable Üretim bağlantıları için uygun sertifika doğrulaması kullanın.
Şifreleme ve sertifikalar bölümünü incele.
Daha fazla bilgi için bkz. Sorun giderme.
Testlerle performans kıyaslamaları.B
Go'nun yerleşik kıyaslama çerçevesini kullanarak veritabanı operasyon performansını ölçebilirsiniz:
func BenchmarkInsert(b *testing.B) {
connString := os.Getenv("TEST_MSSQL_URL")
if connString == "" {
b.Skip("TEST_MSSQL_URL not set")
}
db, err := sql.Open("sqlserver", connString)
if err != nil {
b.Fatalf("open database: %v", err)
}
defer db.Close()
ctx := context.Background()
db.ExecContext(ctx, `
IF OBJECT_ID('dbo.BenchItems', 'U') IS NOT NULL DROP TABLE dbo.BenchItems;
CREATE TABLE dbo.BenchItems (Id INT IDENTITY PRIMARY KEY, Name NVARCHAR(100))`)
b.ResetTimer()
for i := 0; i < b.N; i++ {
db.ExecContext(ctx,
"INSERT INTO dbo.BenchItems (Name) VALUES (@p1)",
sql.Named("p1", fmt.Sprintf("item-%d", i)))
}
b.StopTimer()
db.ExecContext(ctx, "DROP TABLE IF EXISTS dbo.BenchItems")
}
Kıyaslamaları çalıştırın:
go test -bench=BenchmarkInsert -benchmem -count=5
Tam GitHub Actions iş akışı
Bu örnek, SQL Server konteyneri kuran, test şeması oluşturan ve hem birim hem de entegrasyon testlerini çalıştıran tam bir CI boru hattını gösterir:
name: Go SQL Server Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
services:
mssql:
image: mcr.microsoft.com/mssql/server:2025-latest
env:
ACCEPT_EULA: "Y"
MSSQL_SA_PASSWORD: "<password>"
ports:
- 1433:1433
options: >-
--health-cmd "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P '<password>' -C -Q 'SELECT 1'"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.22"
- name: Create test schema
run: |
/opt/mssql-tools18/bin/sqlcmd \
-S localhost -U sa -P "<password>" -C \
-Q "CREATE DATABASE AdventureWorks2025"
/opt/mssql-tools18/bin/sqlcmd \
-S localhost -U sa -P "<password>" -C \
-d AdventureWorks2025 \
-i ./schema/setup.sql
- name: Run unit tests
run: go test -v -short ./...
- name: Run integration tests
env:
TEST_MSSQL_URL: "sqlserver://sa:<password>@localhost:1433?database=AdventureWorks2025"
run: go test -v -race -count=1 ./...
Test hata yolları ve yeniden deneme mantığı
Uygulamanızın geçici hataları ve denemeleri doğru şekilde ele alıp almadığını test edin:
func TestRetryOnTransientError(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatal(err)
}
defer db.Close()
// First call fails with a transient error.
mock.ExpectQuery("SELECT").WillReturnError(fmt.Errorf("mssql: timeout"))
// Second call succeeds.
rows := sqlmock.NewRows([]string{"Id"}).AddRow(1)
mock.ExpectQuery("SELECT").WillReturnRows(rows)
result, err := queryWithRetry(db, "SELECT ProductID FROM Production.Product WHERE ProductID = @p1", 1)
if err != nil {
t.Fatalf("Expected success after retry, got: %v", err)
}
if result != 1 {
t.Errorf("Expected 1, got %d", result)
}
}
Test stratejisi karşılaştırması
| Strategy | Hız | Gerçek Veritabanı | Bağımlılıklar | En iyi kullanım alanları |
|---|---|---|---|---|
testcontainers-go |
Orta (saniye) | Evet | Docker | Çoğu test paketi (önerilen). |
| CI'de Docker | Orta (saniye) | Evet | Docker | GitHub Actions ile CI/CD işlem hatları. |
| İşlemin geri alınması | Hızlı (ms) | Evet | SQL Server | Ortak veritabanında entegrasyon testleri. |
go-sqlmock |
Hızlı (ms) | Hayır | Hiçbiri | Sadece uygulama mantığı için iç döngü birim testleri. |
t.Skip env var ile |
Instant | Hayır | Hiçbiri | Hiç DB olmadığında zarif bir bozulma. |