วิธีจัดการกับคลาสยูทิลิตี้สแตติกเมื่อออกแบบเพื่อทดสอบ


62

เราพยายามออกแบบระบบของเราให้สามารถทดสอบได้และส่วนใหญ่พัฒนาโดยใช้ TDD ขณะนี้เรากำลังพยายามแก้ไขปัญหาต่อไปนี้:

ในสถานที่ต่าง ๆ เราจำเป็นต้องใช้วิธีการช่วยเหลือแบบคงที่เช่น ImageIO และ URLEncoder (ทั้ง Java API มาตรฐาน) และห้องสมุดอื่น ๆ อีกมากมายที่ประกอบด้วยวิธีคงที่ส่วนใหญ่ (เช่นไลบรารี Apache Commons) แต่มันยากมากที่จะทดสอบวิธีการเหล่านั้นที่ใช้คลาสตัวช่วยแบบคงที่

ฉันมีความคิดหลายอย่างสำหรับการแก้ปัญหานี้:

  1. ใช้เฟรมเวิร์กจำลองที่สามารถจำลองคลาสแบบคงที่ (เช่น PowerMock) นี่อาจเป็นทางออกที่ง่ายที่สุด แต่ก็รู้สึกอยากยอมแพ้
  2. สร้างคลาส wrapper ทันทีที่มีรอบยูทิลิตี้คงที่เหล่านั้นเพื่อให้พวกเขาสามารถฉีดเข้าไปในชั้นเรียนที่ใช้พวกเขา ดูเหมือนว่าจะเป็นคำตอบที่ค่อนข้างสะอาด แต่ฉันกลัวว่าเราจะจบลงด้วยการสร้างคลาส wrapper เหล่านั้น
  3. แยกการเรียกคลาสผู้ช่วยแบบคงที่เหล่านี้ลงในฟังก์ชันที่สามารถแทนที่ได้และทดสอบคลาสย่อยของคลาสที่ฉันต้องการทดสอบจริง ๆ

แต่ฉันคิดอยู่เสมอว่านี่จะเป็นปัญหาที่หลายคนต้องเผชิญเมื่อทำ TDD - ดังนั้นจะต้องมีวิธีแก้ไขปัญหานี้อยู่แล้ว

กลยุทธ์ที่ดีที่สุดในการรักษาคลาสที่ใช้ตัวช่วยคงที่สามารถทดสอบได้คืออะไร


ฉันไม่แน่ใจว่าคุณหมายถึง "แหล่งที่เชื่อถือได้และ / หรือเป็นทางการ" แต่ฉันเห็นด้วยกับสิ่งที่ @berecursive เขียนไว้ในคำตอบของเขา PowerMock มีอยู่ด้วยเหตุผลและไม่ควรรู้สึกเหมือน "ยอมแพ้" โดยเฉพาะถ้าคุณไม่ต้องการเขียนคลาส wrapper ด้วยตัวคุณเอง วิธีสุดท้ายและคงที่เป็นความเจ็บปวดเมื่อมาถึงการทดสอบหน่วย (และ TDD) ส่วนตัว? ฉันใช้วิธีที่ 2 ที่คุณอธิบาย
Deco

"แหล่งข้อมูลที่น่าเชื่อถือและ / หรือเป็นทางการ" เป็นเพียงหนึ่งในตัวเลือกที่คุณสามารถเลือกได้เมื่อเริ่มรับรางวัลสำหรับคำถาม ความหมายของฉัน: ประสบการณ์จากหรือการอ้างอิงถึงบทความที่เขียนโดยผู้เชี่ยวชาญ TDD หรือชนิดใด ๆ ของประสบการณ์โดยคนที่ต้องเผชิญกับปัญหาเดียวกัน ...

คำตอบ:


34

(ไม่มีแหล่งที่มา "ทางการ" ที่นี่ฉันกลัว - มันไม่เหมือนว่ามีข้อกำหนดสำหรับวิธีการทดสอบที่ดีเพียงความคิดเห็นของฉันซึ่งหวังว่าจะเป็นประโยชน์)

เมื่อวิธีการคงที่เหล่านี้แสดงถึงการพึ่งพาของแท้สร้าง wrappers ดังนั้นสำหรับสิ่งที่ชอบ:

  • ImageIO
  • ไคลเอ็นต์ HTTP (หรือสิ่งอื่นที่เกี่ยวข้องกับเครือข่าย)
  • ระบบไฟล์
  • รับเวลาปัจจุบัน (ตัวอย่างที่ชื่นชอบของฉันที่ช่วยฉีดพึ่งพา)

... เหมาะสมที่จะสร้างส่วนต่อประสาน

แต่หลายวิธีใน Apache Commons อาจไม่ควรถูกเยาะเย้ย / หลอกลวง ตัวอย่างเช่นใช้วิธีการรวมรายการของสตริงเข้าด้วยกันโดยเพิ่มเครื่องหมายจุลภาคระหว่างรายการ มีจุดใดในเยาะเย้ยเหล่านี้ - เพียงแค่ให้โทรคงทำผลงานได้ตามปกติ คุณไม่ต้องการหรือต้องการแทนที่พฤติกรรมปกติ คุณไม่ได้จัดการกับทรัพยากรภายนอกหรือสิ่งที่ยากต่อการทำงานเป็นเพียงข้อมูล ผลลัพธ์นั้นสามารถคาดเดาได้และคุณไม่ต้องการให้มันเป็นอย่างอื่นนอกจากสิ่งที่มันจะให้กับคุณ

ฉันสงสัยว่าการลบการโทรคงที่ทั้งหมดซึ่งเป็นวิธีการอำนวยความสะดวกที่คาดการณ์ได้ผลลัพธ์ที่ "บริสุทธิ์" (เช่นการเข้ารหัส base64 หรือ URL) แทนที่จะป้อนจุดเข้าสู่ระเบียบจำนวนมากของการพึ่งพาทางตรรกะ (เช่น HTTP) คุณจะพบว่ามันทั้งหมด การปฏิบัติในการทำสิ่งที่ถูกต้องกับการพึ่งพาของแท้


20

นี่เป็นคำถาม / คำตอบที่ให้ความเห็นอย่างแน่นอน แต่สำหรับสิ่งที่คุ้มค่าฉันคิดว่าฉันจะโยนสองเซ็นต์ของฉันในแง่ของรูปแบบ TDD วิธีที่ 2 เป็นวิธีที่แน่นอนตามจดหมาย อาร์กิวเมนต์สำหรับวิธีที่ 2 คือถ้าคุณต้องการแทนที่การใช้งานหนึ่งในคลาสเหล่านั้น - พูดว่าImageIOไลบรารีที่เทียบเท่า - จากนั้นคุณสามารถทำได้ในขณะที่รักษาความมั่นใจในคลาสที่ใช้ประโยชน์จากรหัสนั้น

อย่างไรก็ตามอย่างที่คุณพูดถึงถ้าคุณใช้วิธีการแบบสแตติกจำนวนมากคุณจะต้องเขียนโค้ดตัวห่อมากมาย นี่อาจไม่ใช่สิ่งเลวร้ายในระยะยาว ในแง่ของการบำรุงรักษามีข้อโต้แย้งอย่างนี้ โดยส่วนตัวแล้วฉันต้องการวิธีนี้

ต้องบอกว่า PowerMock มีอยู่ด้วยเหตุผล เป็นปัญหาที่ทราบกันดีว่าการทดสอบเมื่อมีการใช้วิธีการคงที่นั้นมีความเจ็บปวดอย่างรุนแรงดังนั้นการเริ่มต้นของ PowerMock ฉันคิดว่าคุณต้องชั่งน้ำหนักตัวเลือกของคุณในแง่ของจำนวนงานที่จะห่อคลาสตัวช่วยทั้งหมดของคุณกับการใช้ PowerMock ฉันไม่คิดว่ามัน 'ยอมแพ้' ในการใช้ PowerMock - ฉันแค่รู้สึกว่าการห่อคลาสให้คุณมีความยืดหยุ่นมากขึ้นในโครงการขนาดใหญ่ ยิ่งสัญญาสาธารณะ (อินเทอร์เฟซ) คุณสามารถทำให้การแยกระหว่างเจตนาและการนำไปใช้งานสะอาดขึ้น


1
ปัญหาเพิ่มเติมที่ฉันไม่แน่ใจอย่างแน่นอนเกี่ยวกับ: เมื่อใช้งานเครื่องห่อหุ้มคุณจะใช้วิธีการทั้งหมดของชั้นเรียนที่ห่อหรือเพียงแค่สิ่งที่จำเป็นในปัจจุบัน?

3
ในการติดตามแนวคิดที่คล่องตัวคุณควรทำสิ่งที่ง่ายที่สุดที่ใช้ได้ผลและหลีกเลี่ยงการทำงานที่คุณไม่ต้องการ ดังนั้นคุณควรเปิดเผยเฉพาะวิธีการที่คุณต้องการจริงๆ
Assaf Stone

@AssafStone เห็นด้วย

ระวังด้วย PowerMock การจัดการคลาสทั้งหมดที่ต้องทำเพื่อเยาะเย้ยวิธีที่มาพร้อมกับค่าใช้จ่ายจำนวนมาก การทดสอบของคุณจะช้าลงมากถ้าคุณใช้อย่างกว้างขวาง
bcarlso

คุณต้องเขียน wrapper มาก ๆ หรือไม่ถ้าคู่ทดสอบ / ย้ายข้อมูลด้วยการใช้ไลบรารี่ DI / IoC?

4

เพื่อเป็นข้อมูลอ้างอิงสำหรับทุกคนที่จัดการกับปัญหานี้และพบคำถามนี้ฉันจะอธิบายว่าเราตัดสินใจจัดการปัญหานี้อย่างไร:

โดยทั่วไปเราจะติดตามเส้นทางที่ระบุไว้เป็น # 2 (คลาส wrapper สำหรับยูทิลิตี้คงที่) แต่เราจะใช้พวกมันก็ต่อเมื่อมันซับซ้อนเกินไปที่จะให้ยูทิลิตี้ที่มีข้อมูลที่จำเป็นในการสร้างผลลัพธ์ที่ต้องการ (เช่นเมื่อเราต้องจำลองวิธีการอย่างแน่นอน)

ซึ่งหมายความว่าเราไม่จำเป็นต้องเขียน wrapper สำหรับยูทิลิตี้ง่ายๆอย่าง Apache Commons StringEscapeUtils(เพราะสตริงที่พวกเขาต้องการสามารถจัดเตรียมได้ง่าย) และเราไม่ใช้ mocks สำหรับวิธีการคงที่ (ถ้าเราคิดว่าเราอาจต้องใช้เวลาในการเขียน คลาส wrapper และเยาะเย้ยอินสแตนซ์ของ wrapper)


2

ฉันจะทดสอบการเรียนเหล่านี้โดยใช้Groovy Groovy ง่ายต่อการเพิ่มไปยังโครงการ Java ใด ๆ ด้วยคุณสามารถเยาะเย้ยวิธีการคงที่ค่อนข้างง่าย ดูที่การเยาะเย้ยวิธีการแบบคงที่โดยใช้ Groovyสำหรับตัวอย่าง


1

ฉันทำงานให้กับ บริษัท ประกันภัยรายใหญ่และซอร์สโค้ดของเรามีจาวาไฟล์บริสุทธิ์สูงถึง 400MB เราได้พัฒนาแอปพลิเคชั่นทั้งหมดโดยไม่คิดถึง TDD ตั้งแต่เดือนมกราคมปีนี้เราเริ่มต้นด้วยการทดสอบ junit สำหรับแต่ละองค์ประกอบ

ทางออกที่ดีที่สุดในแผนกของเราคือการใช้วัตถุจำลองในวิธีการ JNI บางอย่างที่เชื่อถือได้ของระบบ (เขียนเป็นภาษา C) และคุณไม่สามารถประเมินผลลัพธ์ได้ทุกครั้งในทุกระบบปฏิบัติการ เราไม่มีตัวเลือกอื่นนอกจากใช้คลาสที่เยาะเย้ยและการใช้งานเฉพาะของวิธีการ JNI โดยเฉพาะเพื่อวัตถุประสงค์ในการทดสอบแต่ละโมดูลของแอปพลิเคชันสำหรับทุกระบบปฏิบัติการที่เราสนับสนุน

แต่มันเร็วมากและมันก็ใช้งานได้ดีจนถึงตอนนี้ ฉันแนะนำที่นี่ - http://www.easymock.org/


1

วัตถุโต้ตอบกันเพื่อให้บรรลุเป้าหมายเมื่อคุณมีวัตถุที่ยากต่อการทดสอบเนื่องจากสภาพแวดล้อม (จุดปลาย webservice, dao เลเยอร์เข้าถึงฐานข้อมูล DB, ตัวควบคุมที่จัดการพารามิเตอร์คำขอ http) หรือคุณต้องการทดสอบวัตถุของคุณแยก คุณจำลองวัตถุเหล่านั้น

ความจำเป็นของวิธีการสถิตยศาสตร์จำลองเป็นกลิ่นเหม็นคุณต้องออกแบบแอปพลิเคชันของคุณให้มากขึ้น Object Oriented และวิธีการทดสอบหน่วยยูทิลิตี้แบบคงที่ไม่ได้เพิ่มคุณค่าให้กับโครงการมากขึ้นคลาส wrapper เป็นวิธีที่ดีขึ้นอยู่กับสถานการณ์ เพื่อทดสอบวัตถุเหล่านั้นที่ใช้วิธีการคงที่


1

บางครั้งฉันใช้ตัวเลือก 4

  1. ใช้รูปแบบกลยุทธ์ สร้างคลาสยูทิลิตี้ด้วยวิธีการคงที่ที่มอบหมายการใช้งานกับอินสแตนซ์ของอินเตอร์เฟสแบบเสียบได้ รหัส initializer คงที่ที่ปลั๊กในการใช้งานที่เป็นรูปธรรม เสียบใช้งานจำลองสำหรับการทดสอบ

บางสิ่งเช่นนี้

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 
}

สิ่งที่ฉันชอบเกี่ยวกับวิธีการนี้คือมันทำให้วิธีการยูทิลิตี้คงที่ซึ่งเพียงแค่รู้สึกถูกต้องกับฉันเมื่อฉันพยายามที่จะใช้คลาสตลอดทั้งรหัส

Math.sum(17, 29, 42);
// vs
new Math().sum(17, 29, 42);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.