Bagaimana menangani kelas utilitas statis saat mendesain testabilitas

63

Kami mencoba merancang sistem kami agar dapat diuji dan di sebagian besar dikembangkan menggunakan TDD. Saat ini kami sedang mencoba menyelesaikan masalah berikut:

Di berbagai tempat, kita perlu menggunakan metode pembantu statis seperti ImageIO dan URLEncoder (keduanya API Java standar) dan berbagai perpustakaan lain yang sebagian besar terdiri dari metode statis (seperti perpustakaan Apache Commons). Tetapi sangat sulit untuk menguji metode-metode yang menggunakan kelas pembantu statis tersebut.

Saya punya beberapa ide untuk menyelesaikan masalah ini:

  1. Gunakan kerangka tiruan yang bisa mengejek kelas statis (seperti PowerMock). Ini mungkin solusi paling sederhana tetapi entah bagaimana rasanya menyerah.
  2. Buat kelas pembungkus instantiable di sekitar semua utilitas statis sehingga mereka dapat disuntikkan ke dalam kelas yang menggunakannya. Ini terdengar seperti solusi yang relatif bersih tapi saya khawatir kita akan akhirnya menciptakan banyak sekali kelas pembungkus itu.
  3. Ekstrak setiap panggilan ke kelas helper statis ini ke dalam fungsi yang dapat ditimpa dan menguji subkelas kelas yang sebenarnya ingin saya uji.

Tapi saya terus berpikir bahwa ini hanya harus menjadi masalah yang harus dihadapi banyak orang ketika melakukan TDD - jadi pasti sudah ada solusi untuk masalah ini.

Apa strategi terbaik agar kelas yang menggunakan pembantu statis ini dapat diuji?

Benedikt
sumber
Saya tidak yakin apa yang Anda maksud dengan "sumber terpercaya dan / atau resmi" tetapi saya setuju dengan apa yang telah ditulis @berecursive dalam jawabannya. PowerMock ada karena suatu alasan dan seharusnya tidak terasa seperti "menyerah" terutama jika Anda tidak ingin menulis kelas pembungkus sendiri. Metode final dan statis adalah masalah ketika datang ke unit testing (dan TDD). Sendiri? Saya menggunakan metode 2 yang Anda jelaskan.
Deco
"sumber yang kredibel dan / atau resmi" hanyalah salah satu opsi yang dapat Anda pilih saat memulai hadiah untuk sebuah pertanyaan. Apa yang sebenarnya saya maksud: Pengalaman dari atau referensi ke artikel yang ditulis oleh para ahli TDD. Atau pengalaman apa pun oleh seseorang yang menghadapi masalah yang sama ...

Jawaban:

34

(Tidak ada sumber "resmi" di sini, saya khawatir - tidak seperti ada spesifikasi untuk cara menguji dengan baik. Hanya pendapat saya, yang semoga bermanfaat).

Ketika metode statis ini mewakili dependensi asli , buat pembungkus. Jadi untuk hal-hal seperti:

  • ImageIO
  • Klien HTTP (atau apa pun yang terkait jaringan)
  • Sistem file
  • Mendapatkan waktu saat ini (contoh favorit saya di mana injeksi ketergantungan membantu)

... masuk akal untuk membuat antarmuka.

Tetapi banyak metode di Apache Commons mungkin tidak boleh diejek / dipalsukan. Misalnya, ambil metode untuk bergabung bersama daftar string, menambahkan koma di antara mereka. Tidak ada gunanya mengejek ini - biarkan saja panggilan statis melakukan pekerjaan normal. Anda tidak ingin atau tidak perlu mengganti perilaku normal; Anda tidak berurusan dengan sumber daya eksternal atau sesuatu yang sulit untuk dikerjakan, itu hanya data. Hasilnya mudah ditebak dan Anda tidak akan pernah ingin menjadi sesuatu yang lain dari apa yang akan memberikan pula.

Saya menduga bahwa setelah menghapus semua panggilan statis yang benar-benar merupakan metode kenyamanan dengan hasil "murni" yang dapat diprediksi (seperti pengkodean base64 atau URL) daripada masuk ke kekacauan besar dependensi logis (seperti HTTP) Anda akan menemukan itu sepenuhnya praktis untuk melakukan hal yang benar dengan dependensi asli.

Jon Skeet
sumber
20

Ini jelas merupakan pertanyaan / jawaban, tetapi untuk apa nilainya saya pikir saya akan memasukkan dua sen saya. Dalam hal gaya TDD metode 2 jelas merupakan pendekatan yang mengikutinya ke surat itu. Argumen untuk metode 2 adalah bahwa jika Anda ingin mengganti implementasi salah satu kelas tersebut - katakan ImageIOperpustakaan yang setara - maka Anda bisa melakukannya sambil mempertahankan kepercayaan pada kelas yang memanfaatkan kode itu.

Namun, seperti yang Anda sebutkan, jika Anda menggunakan banyak metode statis maka Anda akan berakhir dengan menulis banyak kode wrapper. Ini mungkin bukan hal buruk dalam jangka panjang. Dalam hal rawatan tentu ada argumen untuk ini. Secara pribadi saya lebih suka pendekatan ini.

Karena itu, PowerMock ada karena suatu alasan. Ini adalah masalah yang cukup terkenal bahwa pengujian ketika metode statis yang terlibat sangat menyakitkan, karena itu lahirnya PowerMock. Saya pikir Anda perlu mempertimbangkan pilihan Anda dalam hal berapa banyak pekerjaan yang akan dilakukan untuk membungkus semua kelas pembantu Anda vs menggunakan PowerMock. Saya tidak berpikir itu 'menyerah' untuk menggunakan PowerMock - Saya hanya merasa bahwa membungkus kelas memungkinkan Anda lebih fleksibel dalam proyek besar. Semakin banyak kontrak publik (antarmuka) Anda dapat memberikan pembersih pemisahan antara niat dan implementasi.


sumber
1
Masalah tambahan yang saya tidak yakin tentang: Ketika menerapkan pembungkus akankah Anda menerapkan semua metode kelas yang dibungkus atau hanya yang saat ini dibutuhkan?
3
Dalam mengikuti ide-ide tangkas, Anda harus melakukan hal paling sederhana yang berhasil, dan menghindari melakukan pekerjaan yang tidak Anda butuhkan. Karena itu, Anda harus memaparkan hanya metode yang benar-benar Anda butuhkan.
Assaf Stone
@AssafStone setuju
Hati-hati dengan PowerMock, semua manipulasi kelas yang harus dilakukan untuk mengejek metode dilengkapi dengan banyak overhead. Tes Anda akan jauh lebih lambat jika Anda menggunakannya secara luas.
bcarlso
Apakah Anda benar-benar harus melakukan banyak pembungkus tulisan jika memasangkan pengujian / migrasi Anda dengan adopsi perpustakaan DI / IoC?
4

Sebagai referensi untuk semua yang juga berurusan dengan masalah ini dan menemukan pertanyaan ini saya akan menjelaskan bagaimana kami memutuskan untuk mengatasi masalah:

Kami pada dasarnya mengikuti jalur yang diuraikan sebagai # 2 (kelas pembungkus untuk utilitas statis). Tetapi kita hanya menggunakannya ketika terlalu rumit untuk menyediakan utilitas dengan data yang diperlukan untuk menghasilkan output yang diinginkan (yaitu ketika kita benar-benar harus mengejek metode).

Ini berarti kita tidak perlu menulis pembungkus untuk utilitas sederhana seperti Apache Commons StringEscapeUtils(karena string yang mereka butuhkan dapat dengan mudah disediakan) dan kita tidak menggunakan ejekan untuk metode statis (jika kita pikir kita mungkin perlu waktu untuk menulis kelas pembungkus dan kemudian mengejek instance pembungkus).

Benedikt
sumber
2

Saya akan menguji kelas-kelas ini menggunakan Groovy . Groovy mudah ditambahkan ke proyek Java apa pun. Dengan itu, Anda dapat mengejek metode statis dengan mudah. Lihat Mengejutkan Metode Statis menggunakan Groovy sebagai contoh.

pengkhianatan
sumber
1

Saya bekerja untuk perusahaan asuransi besar dan kode sumber kami mencapai 400MB file java murni. Kami telah mengembangkan seluruh aplikasi tanpa memikirkan TDD. Dari Januari tahun ini kami mulai dengan pengujian junit untuk setiap komponen individu.

Solusi terbaik di departemen kami adalah menggunakan objek Mock pada beberapa metode JNI yang dapat diandalkan sistem (ditulis dalam C) dan karena itu Anda tidak dapat memperkirakan hasil setiap kali pada setiap OS. Kami tidak memiliki pilihan lain selain menggunakan kelas yang diolok-olok dan implementasi spesifik dari metode JNI khusus untuk tujuan menguji setiap modul aplikasi untuk setiap OS yang kami dukung.

Tapi itu sangat cepat dan sejauh ini sudah berfungsi dengan baik. Saya merekomendasikannya - http://www.easymock.org/


sumber
1

Objek berinteraksi satu sama lain untuk mencapai tujuan, ketika Anda memiliki objek yang sulit untuk diuji karena lingkungan (titik akhir layanan web, lapisan dao mengakses DB, pengendali menangani parameter permintaan http) atau Anda ingin menguji objek Anda secara terpisah, lalu Anda mengejek benda-benda itu.

perlunya mengolok-olok metode statika adalah bau yang tidak sedap, Anda harus mendesain aplikasi Anda lebih Berorientasi Objek, dan unit pengujian utilitas metode statis tidak menambah banyak nilai pada proyek, kelas wrapper adalah pendekatan yang baik tergantung pada situasinya, tetapi cobalah untuk menguji objek-objek yang menggunakan metode statis.


sumber
1

Terkadang saya menggunakan opsi 4

  1. Gunakan pola strategi. Buat kelas utilitas dengan metode statis yang mendelegasikan implementasi ke instance antarmuka pluggable. Kode penginisialisasi statis yang menghubungkan implementasi konkret. Tancapkan implementasi tiruan untuk pengujian.

Sesuatu seperti ini.

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 
}

Apa yang saya sukai dari pendekatan ini adalah ia membuat metode utilitas tetap statis, yang terasa benar bagi saya ketika saya mencoba menggunakan kelas di seluruh kode.

Math.sum(17, 29, 42);
// vs
new Math().sum(17, 29, 42);
bigh_29
sumber