Test edilen sınıfın bir bölümünü taklit etmek sorun olur mu?


22

Bir sınıfım olduğunu varsayalım (kabul edilen örneği ve bunun kötü tasarımını affedin):

class MyProfit
{
  public decimal GetNewYorkRevenue();
  public decimal GetNewYorkExpenses();
  public decimal GetNewYorkProfit();

  public decimal GetMiamiRevenue();
  public decimal GetMiamiExpenses();
  public decimal GetMiamiProfit();

  public bool BothCitiesProfitable();

}

(GetxxxRevenue () ve GetxxxExpenses () yöntemlerinin gizlenmiş bağımlılıkları olduğuna dikkat edin)

Şimdi, GetNewYorkProfit () ve GetMiamiProfit () 'e bağlı BothCitiesProfitable () birimini test ediyorum. GetNewYorkProfit () ve GetMiamiProfit () 'ı saplamak uygun mudur?

Öyle görünmüyorsa, aynı anda GetNewYorkProfit () ve GetMiamiProfit () 'i BothCitiesProfitable () ile birlikte test ediyorum. GetxxxRevenue () ve GetxxxExpenses () için saplama ayarladığımdan emin olmak zorundayım ki GetxxxProfit () yöntemleri doğru değerleri döndürsün.

Şimdiye kadar yalnızca iç yöntemlerde değil dış sınıflarda anlatma bağımlılığı örneği gördüm.

Ve eğer tamamsa, bunu yapmak için kullanmam gereken belirli bir kalıp var mı?

GÜNCELLEŞTİRME

Temel meseleyi özleyemediğimiz için endişeliyim ve bu muhtemelen benim kötü örneğimin hatası. Temel soru şudur: Bir sınıftaki bir yöntemin aynı sınıftaki halka açık başka bir yönteme bağımlılığı varsa, o diğer yöntemi çıkarmak için uygun mu (hatta tavsiye edilir)?

Belki bir şeyleri özlüyorum, ama dersi bölmekten her zaman mantıklı geldiğinden emin değilim. Belki bir başka minimal olarak daha iyi örnek şöyle olabilir:

class Person
{
 public string FirstName()
 public string LastName()
 public string FullName()
}

tam adın tanımlandığı yer:

public string FullName()
{
  return FirstName() + " " + LastName();
}

FullName () işlevini sınarken FirstName () ve LastName () öğesini saplamanız sorun değil mi?


Bazı kodları aynı anda test ederseniz, bu nasıl kötü olabilir? Sorun ne? Normal olarak, kodu daha sık ve daha yoğun şekilde test etmek daha iyi olmalıdır.
kullanıcı bilinmeyen

Güncellemenizi yeni öğrendim, cevabımı güncelledim
Winston Ewert

Yanıtlar:


27

Söz konusu sınıfı bölmelisiniz.

Her sınıf bazı basit işler yapmalıdır. Göreviniz test edemeyecek kadar karmaşıksa, sınıfın yaptığı görev çok büyük demektir.

Bu tasarımın aptallığını gözardı etmek:

class NewYork
{
    decimal GetRevenue();
    decimal GetExpenses();
    decimal GetProfit();
}


class Miami
{
    decimal GetRevenue();
    decimal GetExpenses();
    decimal GetProfit();
}

class MyProfit
{
     MyProfit(NewYork new_york, Miami miami);
     boolean bothProfitable();
}

GÜNCELLEŞTİRME

Bir sınıftaki saplama yöntemleriyle ilgili sorun, kapsüllemeyi ihlal etmenizdir. Testiniz, nesnenin dış davranışının spesifikasyonlara uygun olup olmadığını kontrol etmelidir. Nesnenin içinde ne olursa olsun onun işi değil.

FullName'in FirstName ve LastName kullanıyor olması bir uygulama detayıdır. Sınıfın dışında hiçbir şey bunun gerçek olduğunu umursamalıdır. Nesneyi test etmek için genel yöntemlerle alay ederek, o nesnenin uygulandığına dair bir varsayımda bulunuyorsunuz.

Gelecekte bir noktada, bu varsayımın doğru olması sona erebilir. Belki de tüm ad mantığı, Kişinin basitçe çağırdığı bir Ad nesnesine taşınacaktır. Belki de FullName üye değişkenlerine first_name ve last_name 'e doğrudan erişmek yerine FirstName ve LastName' i çağırır.

İkinci soru, neden böyle yapmak zorunda hissettiğinizdir. Tüm sınıfınız bittikten sonra şöyle bir şey test edilebilir:

Person person = new Person("John", "Doe");
Test.AssertEquals(person.FullName(), "John Doe");

Bu örnek için herhangi bir şeyi saplamaya ihtiyaç duymamalısınız. Eğer yaparsan o zaman çok mutlu ve iyisin ... kes şunu! Buradaki yöntemlerle alay etmenin bir faydası yok çünkü zaten içlerinde ne olduğu konusunda kontrolü elinizde tutuyorsunuz.

FullName'in alay etmek için kullandığı yöntemlerin mantıklı geldiği tek durum, bir şekilde FirstName () ve LastName () 'nin önemsiz işlemler olup olmadığıdır. Belki de bu rasgele isim üreticilerinden birini yazıyorsunuzdur ya da FirstName ve LastName, cevap için veritabanını sorguluyor. Fakat eğer gerçekleşen buysa, nesnenin Person sınıfına ait olmayan bir şey yaptığını gösterir.

Başka bir deyişle, yöntemleri alay etmek, nesneyi almak ve iki parçaya bölmektir. Diğer parça test edilirken bir parça alay ediliyor. Yaptığınız şey, esasen nesnenin ayrılmasından kaynaklanan geçici bir durumdur. Bu durumda, nesneyi zaten ayırın.

Sınıfınız basitse, bir test sırasında bununla alay etme gereğini hissetmemelisiniz. Eğer sınıfınız alay etmek zorunda olduğunuzu hissedecek kadar karmaşıksa, sınıfı daha basit parçalara bölmelisiniz.

TEKRAR GÜNCELLEME

Gördüğüm gibi, bir nesnenin dış ve iç davranışları vardır. Dış davranış, başka nesnelere çağrılar gibi değerler döndürür. Açıkçası, bu kategorideki herhangi bir şey test edilmelidir. (aksi halde neyi test ederdiniz?) Fakat içsel davranış gerçekten test edilmemelidir.

Şimdi iç davranış test edilir, çünkü dış davranışla sonuçlanan budur. Ancak testleri doğrudan içsel davranış üzerine yazmam, sadece dolaylı olarak dış davranış üzerinden.

Bir şeyi test etmek istersem, dış davranışa dönüşmesi için taşınması gerektiğini düşünüyorum. Bu nedenle, bir şeyle alay etmek istiyorsanız, alay etmek istediğiniz şeyin şimdi söz konusu nesnelerin dış davranışında olması için nesneyi ayırmanız gerekir.

Ancak, ne fark eder? FirstName () ve LastName () başka bir nesnenin üyeleriyse, gerçekten FullName () sorununu değiştirir mi? FirstName ile alay etmenin gerekli olduğuna karar verirsek ve LastName aslında başka bir nesnede olmalarına yardımcı olur mu?

Bence alaycı yaklaşımını kullanırsan, nesnede bir dikiş oluşturursun. Doğrudan bir dış veri kaynağıyla iletişim kuran FirstName () ve LastName () gibi işlevleriniz var. Sizde olmayan FullName () de var. Ancak hepsi aynı olmadığı için açık değildir. Bazı parçaların veri kaynağına doğrudan erişmesi gerekmiyor, diğerleri ise. Bu iki grubu dağıtırsanız, kodunuz daha net olacaktır.

DÜZENLE

Geri bir adım atalım ve şunu soralım: Test ederken neden nesnelerle alay ediyoruz?

  1. Testlerin tutarlı bir şekilde çalışmasını sağlayın (çalışmadan koşuya değişen şeylere erişmekten kaçının)
  2. Pahalı kaynaklara erişmekten kaçının (üçüncü taraf hizmetlerine vb. Vurmayın).
  3. Test edilen sistemi basitleştirin
  4. Tüm olası senaryoları test etmeyi kolaylaştırın (örn. Başarısızlık simülasyonu vb.)
  5. Diğer kod parçalarının ayrıntılarına bağlı kalmaktan kaçının, böylece diğer kod parçalarındaki değişiklikler bu testi bozmaz.

Şimdi, neden 1-4'ün bu senaryo için geçerli olmadığını düşünüyorum. Tam adı sınarken harici kaynağın alay edilmesi, alay etmenin tüm bu nedenleriyle ilgilenir. Ele alınmayan tek parça basitliktir, ancak nesnenin bir endişe olmayan yeterince basit olduğu anlaşılmaktadır.

Endişenizin 5 numaralı sebep olduğunu düşünüyorum. Endişe verici, bir noktada FirstName ve LastName'in uygulanmasının değiştirilmesinin bir noktada testi geçeceği yönünde. Gelecekte, FirstName ve LastName, adları farklı bir konumdan veya kaynaktan alabilir. Fakat FullName muhtemelen her zaman olacaktır FirstName() + " " + LastName(). Bu nedenle, FirstName ve LastName ile alay ederek FullName'i test etmek istiyorsunuz.

O zaman sahip olduğunuz kişi nesnesinin, diğerlerinden daha büyük olasılıkla değişmesi muhtemel alt kümesidir. Nesnenin geri kalanı bu alt kümeyi kullanır. Bu alt küme şu anda bir kaynağı kullanarak verilerini alıyor, ancak daha sonraki bir tarihte bu verileri tamamen farklı bir şekilde alabilir. Ama bana göre bu alt küme dışarı çıkmaya çalışan ayrı bir nesne gibi geliyor.

Bana öyle geliyor ki, eğer nesnenin yöntemiyle alay ediyorsanız, nesneyi ayırıyorsunuz. Ama bunu geçici olarak yapıyorsun. Kodunuz Person nesnesinin içinde iki ayrı parça bulunduğunu açıkça belirtmiyor. Bu yüzden basitçe o nesneyi gerçek kodunuzda bölün, böylece kodun ne olup bittiğini okumasından açıkça anlaşılır. Mantıklı olan ve her test için nesneyi farklı şekilde bölmeye çalışmayan nesnenin gerçek bölmesini seçin.

Nesnenizi bölmek için itirazda bulunabileceğinizden şüpheleniyorum, ama neden?

DÜZENLE

Ben hatalıydım.

Bireysel yöntemlerle alay ederek geçici bölmeler eklemekten ziyade nesneleri bölmelisiniz. Ancak, nesneleri bölmenin bir yöntemine fazlasıyla odaklandım. Ancak, OO, bir nesneyi bölmek için birden çok yöntem sağlar.

Ne önereceğim:

class PersonBase
{
      abstract sring FirstName();
      abstract string LastName();

      string FullName()
      {
            return FirstName() + " " + LastName();
      }
 }

 class Person extends PersonBase
 {
      string FirstName(); 
      string LastName();
 }

 class FakePerson extends PersonBase
 {
      void setFirstName(string);
      void setLastName(string);
      string getFirstName();
      string getLastName();
 }

Belki de başından beri yaptığın şey budur. Fakat bu yöntemin alaycı yöntemlerle gördüğüm sorunlara sahip olacağını düşünmüyorum çünkü her yöntemin hangi tarafında olduğunu açıkça belirttik. Ve devralma kullanarak, ek bir sarmalayıcı nesnesi kullanırsak ortaya çıkacak beceriksizliği önleriz.

Bu, biraz karmaşıklık getirmektedir ve sadece birkaç yardımcı fonksiyon için, temelde 3. parti kaynağını alay ederek muhtemelen onları test edeceğim. Elbette, kırılma tehlikesi artar ancak yeniden düzenlenmeye değmez. Eğer ayırmanız gereken yeterince karmaşık bir nesneye sahipseniz, bunun gibi bir şeyin iyi bir fikir olduğunu düşünüyorum.


8
İyi dedi. Testte geçici çözümler bulmaya çalışıyorsanız, muhtemelen tasarımınızı yeniden gözden geçirmeniz gerekir (bir not olarak, şimdi Frank Sinatra'nın kafamın içinde bir ses var).
Sorunlu

2
“Nesneyi test etmek için genel yöntemlerle alay ederek, o nesnenin nasıl uygulandığı hakkında bir varsayımda bulunuyorsunuz.” Ama bir nesneyi sapladığın zaman bu böyle değil mi? Örneğin, xyz () yöntemimin başka bir nesne çağırdığını biliyorum. Xyz () 'yi yalıtımlı olarak test etmek için diğer nesneyi saplamalıyım. Bu nedenle, testim xyz () yöntemimin uygulama ayrıntılarını bilmelidir.
Kullanıcı

Benim durumumda "FirstName ()" ve "LastName ()" yöntemleri basittir ancak sonuçları için üçüncü taraf api sorguları yaparlar.
Kullanıcı

@Kullanıcı, güncellendi, nihayetinde FirstName ve LastName'i test etmek için 3. taraf API ile alay ediyorsunuz. FullName'i sınarken aynı alaycı yapmanın nesi yanlış?
Winston Ewert

Benim bütün tür noktasıdır @Winston, şu anda ben bir test tamadın () için ad ve soyad kullanılan 3. parti api alay, ama nasıl firstname hakkında bakmakta değil tercih ediyorum ve soyadı uygulanmaktadır tamadı test ederken elbette ( İlk adı ve soyadı sınarken sorun değil). Dolayısıyla alaycı ad ve soyadı hakkındaki sorum.
Kullanıcı

10

Winston Ewert'in cevabını kabul ederken, bazen zaman kısıtlamaları, zaten başka bir yerde kullanılmış olan API ya da size ait olan bir sınıftan ayrılmak mümkün olmuyor.

Böyle bir durumda, alaycı yöntemler yerine, dersi adım adım kapsayacak şekilde sınavlarımı yazardım. Test getExpenses()ve getRevenue()ardından test yöntemleri getProfit(), yöntemleri daha sonra paylaşılan yöntemleri test edin. Belirli bir yöntemi kapsayan birden fazla test olacağınız doğrudur, ancak yöntemleri ayrı ayrı kapsayan geçme testleri yazdığınızdan, çıktılarınızın test edilmiş bir girdiyle güvenilir olduğundan emin olabilirsiniz.


1
Kabul. Ancak, bunu okuyan herhangi birinin amacını vurgulamak için, daha iyi bir çözüm yapamıyorsanız kullandığınız çözüm budur.
Winston Ewert 11:11

5
@Winston ve bu, daha iyi bir çözüme geçmeden önce kullandığınız çözümdür. Büyük bir eski kural koduna sahipseniz, yeniden aktarabilmeniz için önce birim testleri ile örtmeniz gerekir.
Péter Török 11:11

@ Péter Török, iyi nokta. Her ne kadar bunun "daha iyi bir çözüm bulamıyor" altında olduğunu düşünüyorum. :)
Winston Ewert 11:11

@all getProfit () yöntemini test etmek çok zor olacaktır çünkü getExpenses () ve getRevenues () için farklı senaryolar aslında getProfit () için gereken senaryoları çoğaltacaktır. getExpenses () 'ı test ediyorsak ve Revenues ()' ı bağımsız olarak alıyorsak, getProfit () testini yapmak için bu yöntemlerle alay etmek iyi bir fikir değil mi?
Mohd Farid,

7

Örneklerinizi daha da basitleştirmek için , her birinin kendi bağımlılığına sahip ve buna C()bağlı olarak test yaptığınızı söyleyin . IMO, testinizin başarmaya çalıştığı şeye iner.A()B()

Eğer davranışını test ediyorsanız C()bilinen verilen davranışlar A()ve B()daha sonra saplama için muhtemelen en kolay ve en iyisi A()veB() . Muhtemelen bu, purists tarafından yapılan bir birim testidir.

Tüm sistemin davranışını test ediyorsanız ( C()bakış açısıyla), bırakacağım A()ve B()uygulayacağım ve ya bağımlılıklarını (test etmeyi mümkün kılıyorsa) ya da test gibi bir sanal alan yaratacağım veri tabanı. Buna entegrasyon testi diyeceğim.

Her iki yaklaşım da geçerli olabilir, bu yüzden eğer tasarlandıysa, onu önden test edebilmeniz için uzun vadede muhtemelen daha iyi olacaktır.

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.