Advertisement
  1. Code
  2. Go

Pengujian Kode Data-Intensif dengan Go, Bagian 3

Scroll to top
Read Time: 8 min
This post is part of a series called Testing Data-Intensive Code with Go.
Testing Data-Intensive Code With Go, Part 2
Testing Data-Intensive Code With Go, Part 4

() translation by (you can also view the original English article)

Ikhtisar

Ini adalah bagian tiga dari dari lima dalam seri tutorial tentang pengujian kode data-intensif dengan Go. Pada bagian kedua, saya membahas pengujian terhadap lapisan data nyata dalam-memori berdasarkan SQLite yang populer. Dalam tutorial ini, saya akan melakukan pengujian terhadap lapisan data kompleks lokal yang menyertakan DB relasional dan cache Redis.

Pengujian Terhadap Lapisan Data Lokal

Pengujian terhadap lapisan data dalam-memori adalah luar biasa. Pengujiannya cepat, dan Anda memiliki kontrol penuh. Tetapi terkadang Anda harus lebih dekat dengan konfigurasi sebenarnya dari lapisan data produksi Anda. Berikut beberapa kemungkinan alasannya:

  • Anda menggunakan detail spesifik dari DB relasional Anda yang ingin Anda uji.
  • Lapisan data Anda terdiri dari beberapa penyimpanan data yang saling berinteraksi.
  • Kode yang diuji terdiri dari beberapa proses yang mengakses lapisan data yang sama.
  • Anda ingin mempersiapkan atau mengamati data uji Anda menggunakan alat standar.
  • Anda tidak ingin menerapkan lapisan data dalam-memori khusus jika lapisan data Anda berubah-ubah.
  • Anda hanya ingin tahu bahwa Anda menguji terhadap lapisan data Anda yang sebenarnya.
  • Anda perlu menguji dengan banyak data yang tidak sesuai dalam memori.

Saya yakin ada alasan lain, tetapi Anda dapat melihat mengapa hanya menggunakan sebuah lapisan data dalam-memori untuk pengujian mungkin tidak cukup dalam banyak kasus.

OK. Jadi kami ingin menguji lapisan data yang sebenarnya. Tapi kita tetap ingin menjadi seringan dan selincah mungkin. Itu berarti lapisan data lokal. Berikut manfaatnya:

  • Tidak perlu menyediakan dan mengkonfigurasi apa pun di pusat data atau cloud.
  • Tidak perlu khawatir tentang pengujian kami yang merusak data produksi secara tidak sengaja.
  • Tidak perlu berkoordinasi dengan sesama pengembang di lingkungan pengujian bersama.
  • Tidak ada kelambatan selama panggilan jaringan.
  • Kontrol penuh atas konten lapisan data, dengan kemampuan untuk memulai dari awal setiap saat.

Dalam tutorial ini kita akan menaikkan taruhannya. Kami akan mengimplementasikan (sebagian) lapisan data hibrid yang terdiri dari DB relasional MariaDB dan server Redis. Kemudian kita akan menggunakan Docker untuk membuat lapisan data lokal yang dapat kita gunakan dalam pengujian kami.

Menggunakan Docker untuk Menghindari Sakit Kepala Saat Instalasi

Pertama, Anda membutuhkan Docker, tentu saja. Lihat dokumentasinya jika Anda tidak terbiasa dengan Docker. Langkah selanjutnya adalah mendapatkan image untuk penyimpanan data kami: MariaDB dan Redis. Tanpa terlalu banyak detail, MariaDB adalah DB relasional hebat yang kompatibel dengan MySQL, dan Redis adalah penyimpanan kunci-nilai dalam-memori yang hebat (dan masih banyak lagi).

1
> docker pull mariadb
2
...
3
4
> docker pull redis
5
...
6
7
> docker images
8
REPOSITORY      TAG      IMAGE ID      CREATED      SIZE
9
mariadb         latest   51d6a5e69fa7  2 weeks ago  402MB
10
redis           latest   b6dddb991dfa  2 weeks ago  107MB

Sekarang setelah Docker diinstal dan kami memiliki image untuk MariaDB dan Redis, kami dapat menulis file docker-compose.yml, yang akan kami gunakan untuk meluncurkan penyimpanan data kami. Mari kita memanggil DB kami "songify".

1
mariadb-songify:
2
  image: mariadb:latest
3
  command: >
4
      --general-log 
5
      --general-log-file=/var/log/mysql/query.log
6
  expose:
7
    - "3306"
8
  ports:
9
    - "3306:3306"
10
  environment:
11
    MYSQL_DATABASE: "songify"
12
    MYSQL_ALLOW_EMPTY_PASSWORD: "true"
13
  volumes_from:
14
    - mariadb-data
15
mariadb-data:
16
  image: mariadb:latest
17
  volumes:
18
    - /var/lib/mysql
19
  entrypoint: /bin/bash
20
21
redis:
22
  image: redis
23
  expose:
24
    - "6379"
25
  ports:
26
    - "6379:6379"

Anda dapat meluncurkan penyimpanan data Anda dengan perintah docker-compose up (mirip dengan vagrant up). Keluarannya akan terlihat seperti ini:

1
> docker-compose up
2
Starting hybridtest_redis_1 ...
3
Starting hybridtest_mariadb-data_1 ...
4
Starting hybridtest_redis_1
5
Starting hybridtest_mariadb-data_1 ... done
6
Starting hybridtest_mariadb-songify_1 ...
7
Starting hybridtest_mariadb-songify_1 ... done
8
Attaching to hybridtest_mariadb-data_1, 
9
             hybridtest_redis_1, 
10
             hybridtest_mariadb-songify_1
11
.
12
.
13
.
14
redis_1  | * DB loaded from disk: 0.002 seconds
15
redis_1  | * Ready to accept connections
16
.
17
.
18
.
19
mariadb-songify_1  | [Note] mysqld: ready for connections.
20
.
21
.
22
.

Pada titik ini, Anda memiliki server MariaDB lengkap mendengarkan pada port 3306 dan server Redis mendengarkan pada port 6379 (keduanya adalah port standar).

Lapisan Data Hibrid

Mari manfaatkan penyimpanan data yang kuat ini dan tingkatkan lapisan data kami ke lapisan data hibrid yang menyimpan lagu-lagu per pengguna dalam Redis. Ketika GetSongsByUser() dipanggil, lapisan data akan memeriksa dulu apakah Redis sudah menyimpan lagu untuk pengguna. Jika itu kemudian hanya mengembalikan lagu dari Redis, tetapi jika tidak (cache miss) maka akan mengambil lagu-lagu dari MariaDB dan mengisi cache Redis, sehingga siap untuk waktu berikutnya.

Berikut adalah definisi struct dan constructor. Struct ini menyimpan handle DB seperti sebelumnya dan juga klien redis. Constructor menghubungkan ke DB relasional serta Redis. Ini menciptakan skema dan flushes redis hanya jika parameter yang sesuai adalah true, yang diperlukan hanya untuk pengujian. Dalam produksi, Anda membuat skema sekali (mengabaikan migrasi skema).

1
type HybridDataLayer struct {
2
    db *sql.DB
3
  redis *redis.Client
4
}
5
6
func NewHybridDataLayer(dbHost string, 
7
                        dbPort int, 
8
                        redisHost string, 
9
                        createSchema bool, 
10
                        clearRedis bool) (*HybridDataLayer, 
11
                                          error) {
12
	dsn := fmt.Sprintf("root@tcp(%s:%d)/", dbHost, dbPort)
13
	if createSchema {
14
		err := createMariaDBSchema(dsn)
15
		if err != nil {
16
			return nil, err
17
		}
18
	}
19
20
	db, err := sql.Open("mysql", 
21
                         dsn+"desongcious?parseTime=true")
22
	if err != nil {
23
		return nil, err
24
	}
25
26
	redisClient := redis.NewClient(&redis.Options{
27
		Addr:     redisHost + ":6379",
28
		Password: "",
29
		DB:       0,
30
	})
31
32
	_, err = redisClient.Ping().Result()
33
	if err != nil {
34
		return nil, err
35
	}
36
37
	if clearRedis {
38
		redisClient.FlushDB()
39
	}
40
41
	return &HybridDataLayer{db, redisClient}, nil
42
}

Menggunakan MariaDB

MariaDB dan SQLite sedikit berbeda sejauh DDL berjalan. Perbedaannya kecil, tetapi penting. Go tidak memiliki piranti lintas-DB yang matang seperti SQLAlchemy yang fantastis dari Python, jadi Anda harus mengelolanya sendiri (tidak, Gorm tidak dihitung). Perbedaan utama adalah:

  • Driver SQL adalah "github.com/go-sql-driver/mysql".
  • Database tidak hidup dalam memori, sehingga dibuat ulang setiap kali (drop dan create).
  • Skema harus merupakan bagian dari pernyataan DDL independen, bukan satu string dari semua pernyataan.
  • Primary key auto increment ditandai oleh AUTO_INCREMENT.
  • VARCHAR bukannya TEXT.

Berikut ini kodenya:

1
func createMariaDBSchema(dsn string) error {
2
    db, err := sql.Open("mysql", dsn)
3
	if err != nil {
4
		return err
5
	}
6
7
	// Recreate DB

8
	commands := []string{
9
		"DROP DATABASE songify;",
10
		"CREATE DATABASE songify;",
11
	}
12
	for _, s := range (commands) {
13
		_, err = db.Exec(s)
14
		if err != nil {
15
			return err
16
		}
17
	}
18
19
	// Create schema

20
	db, err = sql.Open("mysql", dsn+"songify?parseTime=true")
21
	if err != nil {
22
		return err
23
	}
24
25
	schema := []string{
26
		`CREATE TABLE IF NOT EXISTS song (
27
		  id          INTEGER PRIMARY KEY AUTO_INCREMENT,
28
		  url         VARCHAR(2088) UNIQUE,
29
		  title       VARCHAR(100),
30
		  description VARCHAR(500)
31
		);`,
32
		`CREATE TABLE IF NOT EXISTS user (
33
		  id            INTEGER PRIMARY KEY AUTO_INCREMENT,
34
		  name          VARCHAR(100),
35
		  email         VARCHAR(100) UNIQUE,
36
		  registered_at TIMESTAMP,
37
		  last_login    TIMESTAMP
38
		);`,
39
		"CREATE INDEX user_email_idx  ON user (email);",
40
		`CREATE TABLE IF NOT EXISTS label (
41
		  id   INTEGER PRIMARY KEY AUTO_INCREMENT,
42
		  name VARCHAR(100) UNIQUE
43
		);`,
44
		"CREATE INDEX label_name_idx ON label (name);",
45
		`CREATE TABLE IF NOT EXISTS label_song (
46
		  label_id  INTEGER NOT NULL REFERENCES label (id),
47
		  song_id INTEGER NOT NULL REFERENCES song (id),
48
		  PRIMARY KEY (label_id, song_id)
49
		);`,
50
		`CREATE TABLE IF NOT EXISTS user_song (
51
		  user_id INTEGER NOT NULL REFERENCES user (id),
52
		  song_id INTEGER NOT NULL REFERENCES song (id),
53
		  PRIMARY KEY (user_id, song_id)
54
		);`,
55
	}
56
57
	for _, s := range (schema) {
58
		_, err = db.Exec(s)
59
		if err != nil {
60
			return err
61
		}
62
	}
63
	return nil
64
}

Menggunakan Redis

Redis sangat mudah digunakan dari Go. Perpustakaan klien "github.com/go-redis/redis" sangat intuitif dan setia mengikuti perintah Redis. Sebagai contoh, untuk menguji apakah sebuah kunci ada, Anda hanya menggunakan metode Exists() dari klien redis, yang menerima satu atau lebih kunci dan mengembalikan berapa banyak dari mereka ada.

Dalam hal ini, saya memeriksa satu kunci saja:

1
    count, err := m.redis.Exists(email).Result()
2
	if err != nil {
3
		return err
4
	}

Menguji Akses ke Banyak Penyimpanan Data

Pengujiannya sebenarnya identik. Antarmuka tidak berubah, dan perilaku tidak berubah. Satu-satunya perubahan adalah bahwa implementasi sekarang menyimpan cache di Redis. Metode GetSongsByEmail() sekarang hanya memanggil refreshUser_Redis().

1
func (m *HybridDataLayer) GetSongsByUser(u User) (songs []Song, 
2
                                                  err error) {
3
    err = m.refreshUser_Redis(u.Email, &songs)
4
	return
5
}

Metode refreshUser_Redis() mengembalikan lagu-lagu pengguna dari Redis jika ada dan sebaliknya mengambilnya dari MariaDB.

1
type Songs *[]Song
2
3
func (m *HybridDataLayer) refreshUser_Redis(email string, 
4
                                            out Songs) error {
5
    count, err := m.redis.Exists(email).Result()
6
	if err != nil {
7
		return err
8
	}
9
10
	if count == 0 {
11
		err = m.getSongsByUser_DB(email, out)
12
		if err != nil {
13
			return err
14
		}
15
16
		for _, song := range *out {
17
			s, err := serializeSong(song)
18
			if err != nil {
19
				return err
20
			}
21
22
			_, err = m.redis.SAdd(email, s).Result()
23
			if err != nil {
24
				return err
25
			}
26
		}
27
		return
28
	}
29
30
	members, err := m.redis.SMembers(email).Result()
31
	for _, member := range members {
32
		song, err := deserializeSong([]byte(member))
33
		if err != nil {
34
			return err
35
		}
36
		*out = append(*out, song)
37
	}
38
39
	return out, nil
40
}

Ada sedikit masalah di sini dari sudut pandang metodologi pengujian. Ketika kami menguji melalui antarmuka lapisan data abstrak, kami tidak memiliki visibilitas ke dalam implementasi lapisan data.

Sebagai contoh, ada kemungkinan bahwa ada cacat besar di mana lapisan data benar-benar melompati cache dan selalu mengambil data dari DB. Pengujian akan dilalui, tetapi kami tidak mendapatkan manfaat dari cache. Saya akan berbicara di bagian lima tentang menguji cache Anda, yang sangat penting.

Kesimpulan

Dalam tutorial ini, kami membahas pengujian terhadap lapisan data kompleks lokal yang terdiri dari banyak penyimpanan data (DB relasional dan cache Redis). Kami juga menggunakan Docker untuk menyebarkan banyak penyimpanan data dengan mudah untuk diuji.

Di bagian empat, kami akan fokus pada pengujian terhadap penyimpanan data jarak jauh, menggunakan snapshot data produksi untuk pengujian kami, dan juga menghasilkan data uji kami sendiri. Nantikanlah!

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.