Test edilebilirlik için tasarım yaparken statik fayda sınıfları ile nasıl baş edilir


62

Sistemimizi test edilebilir ve TDD kullanılarak geliştirilen parçaların çoğunda tasarlamaya çalışıyoruz. Şu anda aşağıdaki sorunu çözmeye çalışıyoruz:

Çeşitli yerlerde ImageIO ve URLEncoder (hem standart Java API) hem de çoğunlukla statik yöntemlerden (Apache Commons kütüphaneleri gibi) oluşan çeşitli diğer kütüphaneler gibi statik yardımcı yöntemler kullanmamız gerekir. Ancak bu statik yardımcı sınıflarını kullanan yöntemleri test etmek son derece zordur.

Bu sorunu çözmek için birkaç fikrim var:

  1. Statik sınıflarla (PowerMock gibi) dalga geçebilen sahte bir çerçeve kullanın. Bu en basit çözüm olabilir, ancak bir şekilde vazgeçmek gibi hissettiriyor.
  2. Tüm bu statik araçların etrafında somutlaştırılabilir sarıcı sınıfları oluşturun, böylece onları kullanan sınıflara enjekte edilebilirler. Bu göreceli olarak temiz bir çözüm gibi gözüküyor, ancak korkarım ki bu sarıcı sınıflarından çok fazla şey yaratacağız.
  3. Bu statik yardımcı sınıflarına yapılan her çağrıyı, geçersiz kılınabilecek ve gerçekten test etmek istediğim sınıfın bir alt sınıfını test edebilecek bir fonksiyona çıkarın.

Ancak bunun TDD yaparken birçok insanın yüzleşmesi gereken bir sorun olması gerektiğini düşünüyorum - bu yüzden bu sorunun çözümlerinin çoktan olması gerekiyor.

Bu statik yardımcıları kullanan sınıfları test edilebilir tutmak için en iyi strateji nedir?


"Güvenilir ve / veya resmi kaynaklar" ile neyi kastettiğinizden emin değilim ama cevabında @ berecursive'ın yazdıklarına katılıyorum. PowerMock bir nedenden dolayı var ve özellikle sarmalayıcı sınıfları yazmak istemiyorsanız "vazgeçmek" gibi hissetmemeliydi. Son ve statik yöntemler, ünite testi (ve TDD) söz konusu olduğunda acı vericidir. Şahsen? Tanımladığınız yöntem 2'yi kullanıyorum.
Deco

"güvenilir ve / veya resmi kaynaklar", bir soru için lütuf başlatırken seçebileceğiniz seçeneklerden sadece bir tanesidir. Gerçekten demek istediğim: TDD uzmanları tarafından yazılmış makalelerden gelen deneyimler veya referanslar. Veya aynı problemi yaşayan biri tarafından herhangi bir tecrübe ...

Yanıtlar:


34

(Burada "resmi" kaynak yok, korkarım - nasıl iyi bir test yapılacağına dair bir şartname yok. Sadece görüşlerim faydalı olur.)

Bu statik yöntemler orijinal bağımlılıkları temsil ettiğinde , sarmalayıcılar oluşturun. Yani gibi şeyler için:

  • ImageIO
  • HTTP istemcileri (veya ağla ilgili herhangi bir şey)
  • Dosya sistemi
  • Şimdiki zamanı elde etme (bağımlılık enjeksiyonunun yardımcı olduğu yerdeki favori örneğim)

... bir arayüz oluşturmak mantıklıdır.

Ancak Apache Commons'daki yöntemlerin çoğu muhtemelen taklit edilmemelidir . Örneğin, birlikte bir virgül ekleyerek, bir dize listesine katılmak için bir yöntem kullanın. Orada hiçbir nokta bu alay etmeyi - sadece statik çağrı normal çalışma yapalım. Normal davranış yerine geçmek istemez veya gerek; harici bir kaynakla ya da üzerinde çalışmak zor bir şeyle uğraşmıyorsunuz, bu sadece veri. Sonuç tahmin edilebilirdir ve asla size vereceklerinden başka bir şey olmasını istemezsiniz .

Gerçekten bütün mantıksal bağımlılık karmaşasına (HTTP gibi) giriş noktalarından ziyade, öngörülebilir, "saf" sonuçlarla (base64 veya URL kodlaması gibi) kolaylık yöntemleri olan tüm statik çağrıları kaldırdığınızı sanıyorum. Orijinal bağımlılıklarla doğru olanı yapmak için pratik.


20

Bu kesinlikle tartışılan bir soru / cevaptır ancak iki kuruşunu atacağımı düşündüğüm için. TDD tarzı yöntem 2 açısından kesinlikle onu izleyen yaklaşımdır. Metot 2'nin argümanı, eğer bu sınıflardan birinin uygulamasını değiştirmek istediğinizde - ImageIOeşdeğer bir kütüphane söyleyin - o zaman, bu kodu kullanan sınıflara olan güveninizi koruyarak da yapabileceğinizdir.

Ancak, bahsettiğiniz gibi, çok fazla statik yöntem kullanırsanız, o zaman çok sayıda sarmalayıcı kodu yazmanız gerekir. Bu uzun vadede kötü bir şey olmayabilir. Sürdürülebilirlik açısından kesinlikle bunun için argümanlar var. Şahsen ben bu yaklaşımı tercih ederdim.

Bunu söyledikten sonra, PowerMock'un bir nedeni var. Statik yöntemler söz konusu olduğunda yapılan testlerin ciddi şekilde ağrılı olduğu, bu nedenle PowerMock'ın başlangıcında oldukça iyi bilinen bir konudur. Seçeneklerinizi, tüm yardımcı sınıflarınızı sarmanın PowerMock kullanarak kaydırmanın ne kadar işe yarayacağına göre ağırlıklandırmanız gerektiğini düşünüyorum. PowerMock'u kullanmanın 'vazgeçtiğini sanmıyorum' - sadece sınıfları sarmanın büyük bir projede daha fazla esneklik sağladığını düşünüyorum. Kamu ihaleleri (arayüzler) ne kadar fazlasa, temizlik ile niyet ile uygulama arasındaki ayrımı o kadar temizleyebilirsiniz.


1
Tamamen emin olamadığım bir ek sorun: Sarmalayıcıları uygularken, sarılmış sınıfın tüm yöntemlerini mi yoksa şu anda gerekli olanları mı uygulayacaksınız?

3
Çevik fikirleri takip ederken, işe yarayan en basit şeyi yapmalı ve ihtiyacınız olmayan işi yapmaktan kaçınmalısınız. Bu nedenle, yalnızca gerçekten ihtiyacınız olan yöntemleri göstermelisiniz.
Assaf Stone

@AssafStone kabul etti

PowerMock'a dikkat edin, yöntemlerle alay etmek için yapması gereken tüm sınıf manipülasyonlarının bir sürü ek yükü vardır. Kapsamlı kullanırsanız testleriniz çok daha yavaş olacaktır.
bcarlso

Gerçekten de bir DI / IoC kütüphanesinin benimsenmesiyle test / göçmenlik işleminizi yürütürseniz, çok fazla sarıcı yazı yazmanız gerekiyor mu?

4

Aynı zamanda bu problemle uğraşan ve bu soruna rastlayan herkese bir referans olarak, sorunu nasıl ele almaya karar verdiğimizi açıklayacağım:

Temel olarak # 2 olarak belirtilen yolu izliyoruz (statik yardımcı programlar için sarmalayıcı sınıfları). Ancak, bunları yalnızca istenen çıktıyı üretmek için yardımcı programa istenen verileri sağlamak için çok karmaşık olduğunda kullanırız (yani yöntemi kesinlikle alay etmemiz gerektiğinde).

Bu, Apache Commons gibi basit bir yardımcı program için bir sarmalayıcı yazmak zorunda değiliz StringEscapeUtils(çünkü ihtiyaç duydukları dizeler kolayca sağlanabilir) ve statik yöntemler için alay kullanmıyoruz (yazma zamanımızın gerekebileceğini düşünürsek). Bir sarmalayıcı sınıfı ve daha sonra sarmalayıcının bir örneğiyle alay edin).



1

Büyük bir sigorta şirketi için çalışıyorum ve kaynak kodumuz 400 MB’lık saf java dosyasına gidiyor. TDD'yi düşünmeden tüm uygulamayı geliştirdik. Bu yıl ocak ayından itibaren her bir bileşen için junit testi ile başladık.

Bölümümüzde en iyi çözüm, Mock nesnelerini, sistemin güvenilir olduğu (C ile yazılmış) bazı JNI yöntemlerinde kullanmaktı ve bu nedenle sonuçları her işletim sisteminde her zaman tam olarak tahmin edemediniz. Desteklediğimiz her işletim sistemi için uygulamanın her bir modülünü test etmek amacıyla, alaylı sınıfları ve özel JNI yöntemlerini kullanmaktan başka seçeneğimiz yoktu.

Ama bu gerçekten hızlıydı ve şu ana kadar gayet iyi çalışıyordu. Bunu tavsiye ederim - http://www.easymock.org/


1

Nesneler birbirleriyle etkileşime girerek, ortam nedeniyle (webservice son noktası, DB'ye erişen bir dao katmanı, http istek parametrelerini işleyen kontrolörler) ya da DB isteklerine erişen denetleyicileri zorladığınız bir nesneyi elde etmek için birbirleriyle etkileşime girer. Bu nesnelerle alay ediyorsun.

Statik yöntemlerle alay etmenin gerekliliği kötü bir koku, uygulamanızı daha fazla Nesne Yönelimli olarak tasarlamanız gerekiyor ve birim sınama programı statik yöntemleri projeye fazla değer katmıyor, sarıcı sınıfı duruma bağlı olarak iyi bir yaklaşım Statik yöntemleri kullanan nesneleri test etmek için.


1

Bazen seçenek 4'ü kullanıyorum

  1. Strateji kalıbını kullanın. Takılabilir bir arabirim örneğine uygulamayı devredecek statik yöntemlerle bir yardımcı sınıf oluşturun. Somut bir uygulamaya takılan statik bir başlatıcı kodlayın. Test için sahte bir uygulama takın.

Bunun gibi bir şey.

public class DateUtil {
    public interface ITimestampGenerator {
        long getUtcNow();
    }

    class ConcreteTimestampGenerator implements ITimestampGenerator {
        public long getUtcNow() { return System.currentTimeMillis(); }
    }

    private static ITimestampGenerator timestampGenerator;

    static {
        timestampGenerator = new ConcreteTimeStampGenerator;
    }

    public static DateTime utcNow() {
        return new DateTime(timestampGenerator.getUtcNow(), DateTimeZone.UTC);
    }

    public static void setTimestampGenerator(ITimestampGenerator t) {...}

    // plus other util routines, which may or may not use the timestamp generator 
}

Bu yaklaşımdan hoşlandığım şey yardımcı program yöntemlerini statik tutuyor, bu da sınıfı kod boyunca kullanmaya çalışırken kendimi doğru hissediyor.

Math.sum(17, 29, 42);
// vs
new Math().sum(17, 29, 42);
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.