“Belirsiz” dünyada Birim Testi


23

Kendimi bir DDD uzmanı olarak görmüyorum, ancak bir çözüm mimarı olarak mümkün olduğunda en iyi uygulamaları uygulamaya çalışıyorum. DDD’de profesyonel olmayanların ve aleyhtarların (kamu) belirleyicisi "stil" in etrafında çok fazla tartışma olduğunu biliyorum ve tartışmanın her iki tarafını da görebiliyorum. Benim sorunum, her geliştiricinin işleri "doğru" şekilde yapacağına güvenemeyeceğim anlamına gelen beceri, bilgi ve deneyim konusunda geniş bir çeşitliliğe sahip bir ekip üzerinde çalışıyorum. Örneğin, eğer etki alanımızdaki nesneler nesnenin içsel durumundaki değişikliklerin bir yöntemle yapılacağı, ancak kamu mülk belirleyicileri sağlayacak şekilde tasarlanması halinde, birisi yöntemi çağırmak yerine, özelliği kaçınılmaz olarak belirleyecektir. Bu örneği kullanın:

public class MyClass
{
    public Boolean IsPublished
    {
        get { return PublishDate != null; }
    }

    public DateTime? PublishDate { get; set; }

    public void Publish()
    {
        if (IsPublished)
            throw new InvalidOperationException("Already published.");

        PublishDate = DateTime.Today;

        Raise(new PublishedEvent());
    }
}

Benim çözümüm, mülk belirleyicileri özel yapmaktı, çünkü nesneleri hidratlamak için kullandığımız ORM, yansıtıcı kullanıyor, böylece özel belirleyicilere erişebiliyordu. Ancak, bu birim testleri yazmaya çalışırken bir sorun sunar. Örneğin, yeniden yayınlayamayacağımızın gerekliliğini doğrulayan bir birim testi yazmak istediğimde, nesnenin zaten yayınlandığını belirtmem gerekiyor. Bunu iki kez Yayınla'yı arayarak kesinlikle yapabilirim, ancak testim Yayınlama'nın ilk çağrı için doğru şekilde uygulandığını varsayıyor. Bu biraz koklamak görünüyor.

Senaryoyu şu kodla biraz daha gerçek hale getirelim:

public class Document
{
    public Document(String title)
    {
        if (String.IsNullOrWhiteSpace(title))
            throw new ArgumentException("title");

        Title = title;
    }

    public String ApprovedBy { get; private set; }
    public DateTime? ApprovedOn { get; private set; }
    public Boolean IsApproved { get; private set; }
    public Boolean IsPublished { get; private set; }
    public String PublishedBy { get; private set; }
    public DateTime? PublishedOn { get; private set; }
    public String Title { get; private set; }

    public void Approve(String by)
    {
        if (IsApproved)
            throw new InvalidOperationException("Already approved.");

        ApprovedBy = by;
        ApprovedOn = DateTime.Today;
        IsApproved = true;

        Raise(new ApprovedEvent(Title));
    }

    public void Publish(String by)
    {
        if (IsPublished)
            throw new InvalidOperationException("Already published.");

        if (!IsApproved)
            throw new InvalidOperationException("Cannot publish until approved.");

        PublishedBy = by;
        PublishedOn = DateTime.Today;
        IsPublished = true;

        Raise(new PublishedEvent(Title));
    }
}

Aşağıdakileri doğrulayan birim testleri yazmak istiyorum:

  • Belge onaylanmadıkça yayınlayamam
  • Bir Dokümanı tekrar yayınlayamıyorum
  • Yayımlandığında PublishedBy ve PublishedOn değerleri uygun şekilde ayarlandı
  • Yayına girdiğinde PublishedEvent yükseltildi

Belirleyicilere girmeden, nesneyi testleri gerçekleştirmek için gereken duruma koyamıyorum. Ayarlayıcılara erişimi açmak, erişimi engelleme amacını yendi.

Bu problemi nasıl çözersiniz (d)?


Bu konuda ne kadar çok düşünürsem, tüm probleminizin yan etkileri olan yöntemler olduğunu düşünüyorum. Veya daha doğrusu, değişken bir değişmez nesne. Bir DDD dünyasında, bu nesnenin iç durumunu güncellemek yerine, hem Onay hem de Yayınlama'dan yeni bir Doküman nesnesi döndürmemeli misiniz?
pdr

1
Hızlı soru, hangi O / RM kullanıyorsunuz. EF’in büyük bir hayranıyım ama ayarlayıcıları korumalı olarak ilan etmek beni yanlış yönlendiriyor.
Michael Brown

Şu anda içeri girdiğim için uğraştığım serbest gelişimden dolayı bir karışımımız var. Bazı ADO.NET, bir DataReader’dan, birkaç Linq’den SQL’e (birkaç sonraki SQL modelinde) olacak bir DataReader’dan hidrate etmek için AutoMapper kullanarak ) ve bazı yeni EF modelleri.
SonOfPirate

İki kez Yayınlama'yı çağırmak hiç kokmuyor ve bunu yapmanın yolu da bu.
Piotr Perak

Yanıtlar:


27

Nesneyi testleri gerçekleştirmek için gereken duruma getiremiyorum.

Nesneyi test yapmak için gereken duruma koyamazsanız, nesneyi üretim kodunda bulunduğu duruma koyamazsınız, bu nedenle bu durumu test etmeye gerek kalmaz . Açıkçası, bu durumda doğru değil, olabilir sadece onayla diyoruz, gerekli duruma Objenizi koydu.

  • Belge onaylanmadıkça yayınlayamıyorum: çağrı onaylamadan önce çağrı yayınlamanın, nesne durumunu değiştirmeden doğru hataya neden olduğunu test eden bir yazı yazın.

    void testPublishBeforeApprove() {
        doc = new Document("Doc");
        AssertRaises(doc.publish, ..., NotApprovedException);
    }
    
  • Bir Belgeyi yeniden yayınlayamıyorum: bir nesneyi onaylayan bir test yazarım, ardından başarılı olduktan sonra yayınlamayı çağırırım, ancak ikinci kez nesne durumunu değiştirmeden doğru hataya neden olur.

    void testRePublish() {
        doc = new Document("Doc");
        doc.approve();
        doc.publish();
        AssertRaises(doc.publish, ..., RepublishException);
    }
    
  • Yayımlandığında PublishedBy ve PublishedOn değerleri doğru bir şekilde ayarlandı: çağrıları onaylayan bir test yazın, ardından çağrı yayınlayın, nesne durumunun doğru değiştiğini iddia edin

    void testPublish() {
        doc = new Document("Doc");
        doc.approve();
        doc.publish();
        Assert(doc.PublishedBy, ...);
        ...
    }
    
  • Yayınlandığında PublishedEvent yükseltilir: etkinlik sistemine bağlanın ve çağrıldığından emin olmak için bir bayrak belirleyin

Ayrıca onaylamak için test yazmanız gerekir.

Başka bir deyişle, iç alanlar ile IsPublished ve IsApproved arasındaki ilişkiyi test etmeyin, eğer alanınızı değiştirmek test kodunuzu değiştirmek anlamına geleceğinden testiniz oldukça kırılgan olacaktır, bu nedenle test oldukça anlamsız olacaktır. Bunun yerine, genel yöntem çağrıları arasındaki ilişkiyi test etmelisiniz, bu şekilde, alanları değiştirseniz bile testi değiştirmeniz gerekmez.


Onayları kesildiğinde, birkaç test kesilir. Artık bir kod birimini test etmiyorsunuz, tam uygulamayı test ediyorsunuz.
pdr

PDR'nin endişesini paylaşıyorum, bu yüzden bu yöne gitmekte tereddüt ettim. Evet, en temiz görünüyor, ancak bireysel bir testin başarısız olabileceği birden fazla nedene sahip olmayı sevmiyorum.
SonOfPirate

4
Sadece olası bir nedenden dolayı başarısız olabilecek bir birim testi görmedim. Ayrıca, testin "durum manipülasyonu" bölümlerini bir setup()yönteme --- testin kendisine koyabilirsiniz .
Peter K.

12
Neden bir approve()şekilde kırılgan olmasına rağmen, bir setApproved(true)şekilde bağımlı değil mi? approve()testlerde meşru bir bağımlılıktır, çünkü gereksinimlerde bir bağımlılıktır. Bağımlılık yalnızca testlerde mevcut olsaydı, bu başka bir sorun olurdu.
Karl Bielefeldt

2
@pdr, yığın sınıfını nasıl test edersiniz? push()Ve pop()yöntemleri bağımsız olarak test etmeye çalışır mısın?
Winston Ewert

2

Diğer bir yaklaşım ise, iç özelliklerin somutlaştırmaya alınmasını sağlayan bir sınıf kurucusu oluşturmaktır:

 public Document(
  String approvedBy,
  DateTime? approvedOn,
  Boolean isApproved,
  Boolean isPublished,
  String publishedBy,
  DateTime? publishedOn,
  String title)
{
  ApprovedBy = approvedBy;
  ApprovedOn = approvedOn;
  IsApproved = isApproved;
  IsApproved = isApproved;
  PublishedBy = publishedBy;
  PublishedOn = publishedOn;
}

2
Bu hiç iyi ölçeklenmiyor. Nesnem, nesnenin yaşam döngüsünün herhangi bir noktasında herhangi bir noktada değerleri olan veya olmayan pek çok özelliğe sahip olabilir. Yapıcıların, nesnenin geçerli bir başlangıç ​​durumunda olması veya bir nesnenin çalışması için ihtiyaç duyduğu bağımlılıkta olması gereken özellikler için parametreler içerdiği prensibini takip ediyorum. Örnekteki özelliklerin amacı, nesne manipüle edilirken mevcut durumu yakalamaktır. Her özelliği olan bir yapıcıya sahip olmak veya farklı kombinasyonlarda aşırı yüklenmek büyük bir koku ve dediğim gibi ölçeklenmiyor.
SonOfPirate

Anladım. Örnekte çok daha fazla özellikten bahsetmediniz ve örnekteki sayı, bunun geçerli bir yaklaşım olduğu konusunda "zirvede". Eğer: size tasarım hakkında bir şeyler anlatıyor gibi gözükmektedir edemez içine nesne koymak herhangi örneği başlatıldığında geçerli duruma. Bu, geçerli bir başlangıç ​​durumuna koymanız ve test için doğru duruma yönlendirmeniz gerektiği anlamına gelir. Bu Lie Ryan'ın cevabını gitmenin yoludur .
Peter K.

Nesne bir özelliğe sahip olsa ve asla değişmeyecek olsa bile bu çözüm kötüdür. Birinin bu yapıcıyı üretimde kullanmasını engelleyen nedir? Bu yapıcıyı [TestOnly] nasıl işaretlersiniz?
Piotr Perak

Üretimde neden kötü? (Gerçekten bilmek istiyorum). Bazen bir nesnenin kesin halini sadece bir geçerli başlangıç ​​nesnesini değil, yaratım sırasında ... yeniden oluşturmak gerekir.
Peter K.

1
Bu, nesneyi geçerli bir başlangıç ​​durumuna getirmeye yardımcı olurken, nesnenin yaşam döngüsü boyunca devam ettiği davranışını test etmek, nesnenin başlangıç ​​durumundan değiştirilmesini gerektirir. OP'm, nesnenin durumunu değiştirmek için özellikleri ayarlayamadığınızda bu ek durumları test etmekle ilgilidir.
SonOfPirate

1

Stratejilerden biri, sınıfı (bu durumda Belge'de) miras almanız ve miras almış sınıfa karşı testler yazmanızdır. Miras alınan sınıf, nesne durumunu sınamalarda ayarlamanın bir yolunu sağlar.

C # 'da bir strateji ayarlayıcıları dahili yapmak, ardından projeyi test etmek için iç kısımları göstermek olabilir.

Sınıf API'sini tanımladığınız gibi kullanabilirsiniz ("Bunu iki kez Publish'i arayarak kesinlikle yapabilirim"). Bu, nesnenin kamu hizmetlerini kullanarak nesne durumunu belirliyor olacak, bana çok kokulu görünmüyor. Örneğiniz durumunda, bu muhtemelen benim yaptığım gibi olacaktır.


Bunun olası bir çözüm olduğunu düşündüm, ancak mülklerimi geçersiz kılmak veya ayarlayıcıları korumalı olarak göstermek için tereddüt ettim; Mülkiyetleri korumanın kesinlikle halktan, hatta içten / arkadaştan daha iyi olduğunu düşünüyorum. Bu yaklaşıma kesinlikle daha fazla düşünce vereceğim. Çok basit ve etkili. Bazen en iyi yaklaşım budur. Herhangi biri aynı fikirde değilse, lütfen özellikleri olan yorumlar ekleyin.
SonOfPirate

1

Mutlak izolasyonda etki alanı nesnelerinin aldığı komutları ve sorguları sınamak için, her sınamayı, beklenen durumda nesnenin bir seri hale getirilmesini sağlamaya alışkınım. Testin düzenleme bölümünde, daha önce hazırladığım bir dosyadan test edilecek nesneyi yüklüyor. İlk başta ikili serileştirmelerle başladım, ancak json'un yönetimi çok daha kolaydı. Bu, testlerde mutlak izolasyonun gerçek değeri sağladığı zaman iyi çalıştığını kanıtladı.

Sadece bir not düzenleme , bazı zamanlarda JSON serileştirme başarısız olur (döngüsel nesnenin grafikleri durumunda, bu bir koku, btw). Bu gibi durumlarda, ikili serileştirmeyi kurtarıyorum. Biraz pragmatik ama işe yarıyor. :-)


Ve bir düzenleyici yoksa ve onu ayarlamak için ortak yöntem olarak adlandırmak istemiyorsanız, beklenen durumda nesneyi nasıl hazırlarsınız?
Piotr Perak

Bunun için küçük bir araç yazdım. Genel yapıcısını kullanarak (genellikle yalnızca tanımlayıcıyı alarak) yeni bir varlık oluşturan ve her işlemden sonra bir anlık görüntü kaydederek (eylemin dizinine dayanan geleneksel bir adla) bir Eylem dizisi <TEntity> çağıran bir yansıma sınıfını yükler ve işletme adı). Araç, varlık kodunun her yeniden denetiminde manuel olarak çalıştırılır ve anlık görüntüler DCVS tarafından izlenir. Açıkçası, her bir Eylem, varlığın halka açık bir komutunu çağırır, ancak bu, bu şekilde gerçekten Birim testi olduğu testlerden yapılır .
Giacomo Tesio

Bunun bir şeyi nasıl değiştirdiğini anlamıyorum. Eğer hala sut üzerinde kamuya açık metotları çağırıyorsa (test edilen sistem) o zaman farklı değildir, sadece testte bu metodları çağırır.
Piotr Perak

Anlık görüntüler üretildikten sonra dosyalarda depolanır. Her test, varlığın başlangıç ​​durumunu elde etmek için gereken işlemlerin sırasına bağlı değildir, ancak durumun kendisinden (anlık görüntüden yüklenir) bağlıdır. Testin altındaki yöntem daha sonra diğer yöntemlerde yapılan değişikliklerden izole edilir.
Giacomo Tesio

Birisi testleriniz için serileştirilmiş durum hazırlamak için kullanılan ortak yöntemi değiştirdiğinde, ancak serileştirilmiş nesneyi yeniden oluşturmak için aracı çalıştırmayı unuttuğunda ne olur? Kodda bir hata olsa bile testler hala yeşil. Yine de bunun hiçbir şeyi değiştirmeyeceğini söylüyorum. Herkese açık yöntemler kullanıyorsunuz, bu yüzden test ettiğiniz nesneleri ayarlayın. Ama testleri denemeden çok önce onları çalıştırıyorsun.
Piotr Perak

-7

Diyorsun

mümkün olduğunda en iyi uygulamaları uygulamaya çalışın

ve

Nesneleri hidratlamak için kullandığımız ORM, yansıma kullanır, böylece özel ayarlayıcılara erişebilir.

ve sınıflarınızdaki erişim kontrollerini atlamak için yansıma kullanmanın "en iyi uygulama" olarak tanımladığım gibi olmadığını düşünmem gerekiyor. O da çok yavaş olacak.


Şahsen, senin birim test çerçeveni mahveder ve sınıfta bir şeyle giderdim - görünüşe göre tüm sınıfı test etme açısından testler yazıyorsun, ki bu iyi bir şey. Geçmişte, test edilmesi gereken bazı zorlu bileşenler için, iddiaları ve kurulum kodunu sınıfın içine gömdüm (her sınıfta bir test () yöntemine sahip olmak için ortak bir tasarım deseniydı), bu yüzden bir müşteri yarattınız. Bu sadece bir nesneyi başlatır ve yansıma kesmek gibi boş olmadan kendi istediğin gibi ayarlayabilen test yöntemini çağırır.

Kod şişirme konusunda endişeleriniz varsa, yalnızca bunları hata ayıklama kodunda kullanılabilir yapmak için #ifdefs'deki test yöntemlerini kullanın (muhtemelen en iyi uygulama)


4
-1: Test çerçevenizi hurdaya çıkarmak ve sınıf içindeki test yöntemlerine geri dönmek, ünite testinin karanlık dönemlerine geri dönecektir.
Robert Johnson

9
Benden -1, ancak üretimde test kodu dahil genellikle bir Bad Thing (TM) 'dir .
Peter K.

OP başka neler yapar? Özel ayarlayıcılarla takılmaya çalışılsın mı ?! Hangi zehiri içmek istediğinizi seçmek gibi. OP'ye önerim, birim testini üretim yerine hata ayıklama koduna koymaktı. Tecrübelerime göre, ünite testlerini farklı bir projeye koymak, projenin yine de aslına yakından bağlı olduğu anlamına gelir, bu yüzden dev bir PoV'dan, çok az bir ayrım vardır.
gbjbaanb
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.