OS Exec

keith6014, reddit'te soruyor
os/exec.Command() kullanarak XML verisi üreten bir komut çalıştırıyorum. Bu komut, GetData() adlı bir fonksiyon içinde çalıştırılacak.
GetData()'yı test etmek için kendi oluşturduğum bir test verisine sahibim.
_test.go dosyamda TestGetData adında bir testim var ve bu test, GetData() fonksiyonunu çağırıyor, ancak bu fonksiyon os.exec kullanıyor. Bunun yerine test verilerimi kullanmasını istiyorum.
Bunu başarmanın iyi bir yolu nedir? GetData'yı çağırırken bir "test" bayrak moduna sahip olmalı mıyım, böylece GetData(mode string) gibi bir dosyayı okuyacak mı?
Bir kaç şey;
  • Bir şeyin test edilmesi zor olduğunda, bu genellikle endişelerin ayrılmasının pek doğru olmamasından kaynaklanır.
  • Kodunuza "test modları" eklemeyin, yerine Dependency Injection kullanın, böylelikle endişeleri ayırabilir ve kendi bağımlılıklarınızı modelleyebilirsiniz.
Kodun neye benzeyeceğini tahmin etmeye çalıştım:
type Payload struct {
Message string `xml:"message"`
}
func GetData() string {
cmd := exec.Command("cat", "msg.xml")
out, _ := cmd.StdoutPipe()
var payload Payload
decoder := xml.NewDecoder(out)
// these 3 can return errors but I'm ignoring for brevity
cmd.Start()
decoder.Decode(&payload)
cmd.Wait()
return strings.ToUpper(payload.Message)
}
  • Sürece harici bir komut yürütmenize izin veren exec.Commandı kullanır.
  • Çıktıyı cmd.StdoutPipeta yakalıyoruz ve bu bıze bır io.ReadCloser döndürüyor (bu ileride önemli olacak).
  • Kodun az çok geri kalan kısmı mükemmel dokümantasyondan kopyalanıp yapıştırılmıştır.
    • Stdout'tan herhangi bir çıktıyı io.ReadCloser içine alıyoruz ve komutu başlatıyoruz, ardından Waiti çağırarak tüm verilerin okunmasını bekliyoruz. Bu iki çağrı arasında verinin kodunu 'Payload' yapımızda çözüyoruz.
İşte msg.xml içinde bulunanlar:
<payload>
<message>Happy New Year!</message>
</payload>
Bunu uygulamalı olarak göstermek için basit bir test yazdım:
func TestGetData(t *testing.T) {
got := GetData()
want := "HAPPY NEW YEAR!"
if got != want {
t.Errorf("got %q, want %q", got, want)
}
}

Test edilebilir kod

Test edilebilir kod ayrıştırılmış ve tek amaçlıdır. Bana, bu kod için iki ana endişe var gibi geliyor:
  1. 1.
    Ham XML verilerini alma
  2. 2.
    XML verilerinin kodunun çözülmesi ve iş mantığımızın uygulanması (bu senaryoda <message>daki strings.ToUpper)
İlk kısım sadece standart kütüphanedeki örneği kopyalıyor.
İkinci kısım iş mantığımızın oluştuğu yerdir ve koda bakarak mantığımızdaki "dikişin" nerede başladığını görebiliriz; io.ReadCloserımızı aldığımız yer burasıdır. Bu mevcut soyutlamayı endişeleri ayırmak ve kodumuzu test edilebilir hale getirmek için kullanabiliriz.
GetData ile ilgili sorun, iş mantığının XML alma araçlarıyla bağlantılı olmasıdır. Tasarımımızı daha iyi hale getirmek için onları ayırmamız gerekiyor
TestGetDatamız iki endişemiz arasındaki entegrasyon testimiz olarak hareket edebilir, bu yüzden çalışmaya devam ettiğinden emin olmak için onu elimizde tutacağız.
İşte yeni ayırılmış kod böyle gözüküyor:
type Payload struct {
Message string `xml:"message"`
}
func GetData(data io.Reader) string {
var payload Payload
xml.NewDecoder(data).Decode(&payload)
return strings.ToUpper(payload.Message)
}
func getXMLFromCommand() io.Reader {
cmd := exec.Command("cat", "msg.xml")
out, _ := cmd.StdoutPipe()
cmd.Start()
data, _ := io.ReadAll(out)
cmd.Wait()
return bytes.NewReader(data)
}
func TestGetDataIntegration(t *testing.T) {
got := GetData(getXMLFromCommand())
want := "HAPPY NEW YEAR!"
if got != want {
t.Errorf("got %q, want %q", got, want)
}
}
Artık GetData girdisini yalnızca io.Readerdan aldığından onu test edilebilir hale getirdik ve artık verilerin nasıl alındığıyla ilgilenmiyoruz; insanlar bu işlevi io.Reader döndüren herhangi bir şeyle yeniden kullanabilirler (ki bu son derece yaygındır). Örneğin XML'i komut satırı yerine bir URL'den almaya başlayabiliriz.
func TestGetData(t *testing.T) {
input := strings.NewReader(`
<payload>
<message>Cats are the best animal</message>
</payload>`)
got := GetData(input)
want := "CATS ARE THE BEST ANIMAL"
if got != want {
t.Errorf("got %q, want %q", got, want)
}
}
İşte GetData için bir birim testi örneği.
Go testinde endişeleri ayırarak ve mevcut soyutlamaları kullanarak önemli iş mantığımızı kullanmak çocuk oyuncağıdır.
Bu sayfa @rasimthegrey tarafından çevrildi.