การทดสอบตามตัวอย่าง

เผยแพร่แล้ว: 2022-02-15

เรากำลังดำเนินการกับชุดบล็อกของเราเกี่ยวกับทุกสิ่งที่เกี่ยวข้องกับการทดสอบ ในบล็อกนี้ เราจะเน้นที่ตัวอย่างจริง

ในขณะที่ตัวอย่างในโพสต์นี้เขียนโดยใช้ JUnit 5 และ AssertJ บทเรียนนี้สามารถใช้ได้กับกรอบงานการทดสอบหน่วยอื่นๆ

JUnit เป็นเฟรมเวิร์กการทดสอบที่ได้รับความนิยมมากที่สุดสำหรับ Java AssertJ เป็นไลบรารี Java ที่ช่วยให้นักพัฒนาเขียนการทดสอบที่แสดงออกมากขึ้น

โครงสร้างการทดสอบพื้นฐาน

ตัวอย่างแรกของการทดสอบที่เราจะดูคือเครื่องคำนวณง่ายๆ สำหรับการบวกตัวเลข 2 ตัว

 เครื่องคิดเลขคลาสควร {

     @ทดสอบ // 1
     ผลรวมเป็นโมฆะ () {
         เครื่องคิดเลขเครื่องคิดเลข = เครื่องคิดเลขใหม่ (); // 2
         ผลลัพธ์ int = calculator.sum (1, 2); // 3
         ยืนยันนั่น(ผลลัพธ์).isEqualTo(3); // 4
     }
}

ฉันชอบใช้แบบแผนการตั้งชื่อ ClassShould เมื่อเขียนการทดสอบเพื่อหลีกเลี่ยงการทำซ้ำ should หรือ test ในทุกชื่อเมธอด คุณสามารถอ่านเพิ่มเติมเกี่ยวกับเรื่องนี้ได้ที่นี่

การทดสอบข้างต้นทำอะไร?

มาแบ่งบรรทัดทดสอบทีละบรรทัด:

  1. คำอธิบายประกอบ @Test ช่วยให้เฟรมเวิร์ก JUnit รู้ว่าเมธอดใดที่ควรรันเป็นแบบทดสอบ เป็นเรื่องปกติอย่างยิ่งที่จะมีเมธอด private ในคลาสการทดสอบซึ่งไม่ใช่การทดสอบ
  2. นี่คือขั้นตอนการ จัดการ ทดสอบของเรา ซึ่งเราเตรียมสภาพแวดล้อมการทดสอบ ทั้งหมดที่เราต้องการสำหรับการทดสอบนี้คือมีอินสแตนซ์ของ Calculator
  3. นี่คือขั้นตอน การดำเนิน การที่เราเรียกพฤติกรรมที่เราต้องการทดสอบ
  4. นี่คือระยะ ยืนยัน ซึ่งเราตรวจสอบสิ่งที่เกิดขึ้นและหากทุกอย่างได้รับการแก้ไขตามที่คาดไว้ assertThat(result) เป็นส่วนหนึ่งของไลบรารี AssertJ และมีการโอเวอร์โหลดหลายครั้ง

การโอเวอร์โหลดแต่ละครั้งจะส่งคืนอ็อบเจ็กต์ Assert พิเศษ วัตถุที่ส่งคืนมีวิธีการที่เหมาะสมกับวัตถุที่เราส่งผ่านไปยังวิธี assertThat ในกรณีของเรา วัตถุนั้นคือ AbstractIntegerAssert พร้อมเมธอดสำหรับการทดสอบจำนวนเต็ม isEqualTo(3) จะตรวจสอบว่า result == 3 หรือไม่ หากเป็นเช่นนั้น การทดสอบจะผ่านและไม่ผ่าน

เราจะไม่เน้นการใช้งานใด ๆ ในบล็อกโพสต์นี้

อีกวิธีหนึ่งในการคิดเกี่ยวกับ Arrange , Act , Assert คือ ให้ เมื่อไหร่ และ แล้ว

หลังจากที่เราเขียน sum ของเราแล้ว เราสามารถถามตัวเองบางคำถาม:

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

มาเพิ่มกรณีเหล่านี้และปรับปรุงชื่อการทดสอบที่มีอยู่กันเล็กน้อย

เราจะไม่อนุญาตให้โอเวอร์โฟลว์ในการใช้งานของเรา หาก sum ล้น เราจะโยน ArithmeticException แทน

 เครื่องคิดเลขคลาสควร {

     เครื่องคิดเลขเครื่องคิดเลขส่วนตัว = เครื่องคิดเลขใหม่ ();

     @ทดสอบ
     เป็นโมฆะ sumPositiveNumbers () {
         int sum = calculator.sum(1, 2);
         ยืนยันนั่น(ผลรวม).isEqualTo(3);
     }

     @ทดสอบ
     เป็นโมฆะ sumNegativeNumbers () {
         int sum = calculator.sum(-1, -1);
         ยืนยันนั่น(ผลรวม).isEqualTo(-2);
     }

     @ทดสอบ
     เป็นโมฆะ sumPositiveAndNegativeNumbers () {
         int sum = calculator.sum(1, -2);
         ยืนยันนั่น(ผลรวม).isEqualTo(-1);
     }

     @ทดสอบ
     เป็นโมฆะ failWithArithmeticExceptionWhenOverflown () {
         assertThatThrownBy(() -> calculator.sum(Integer.MAX_VALUE, 1))
             .isInstanceOf(ArithmeticException.class);
     } 

}

JUnit จะสร้างอินสแตนซ์ใหม่ของ CalculatorShould ก่อนเรียกใช้แต่ละวิธี @Test นั่นหมายความว่า CalculatorShould แต่ละตัวจะมี calculator ต่างกัน ดังนั้นเราไม่ต้องยกตัวอย่างในการทดสอบทุกครั้ง

shouldFailWithArithmeticExceptionWhenOverflown test ใช้ assert ชนิดอื่น จะตรวจสอบว่าโค้ดบางส่วนล้มเหลว assertThatThrownBy วิธีจะเรียกใช้แลมบ์ดาที่เราจัดเตรียมไว้และตรวจสอบให้แน่ใจว่าล้มเหลว ดังที่เราทราบแล้ว วิธีการ assertThat ทั้งหมดจะคืนค่า Assert แบบพิเศษ ทำให้เราสามารถตรวจสอบว่าเกิดข้อยกเว้นประเภทใด

นี่คือตัวอย่างวิธีที่เราสามารถทดสอบว่าโค้ดของเราล้มเหลวเมื่อเราคาดหวัง หาก ณ จุดใดที่เราปรับโครงสร้าง Calculator ใหม่และไม่ได้ส่ง ArithmeticException ไปที่โอเวอร์โฟลว์ การทดสอบของเราจะล้มเหลว

วัตถุรูปแบบการออกแบบของแม่

ตัวอย่างต่อไปคือคลาสเครื่องมือตรวจสอบเพื่อให้แน่ใจว่าอินสแตนซ์บุคคลนั้นถูกต้อง

 คลาส PersonValidatorShould {

    เครื่องมือตรวจสอบ PersonalValidator ส่วนตัว = PersonValidator ใหม่ ();

    @ทดสอบ
    เป็นโมฆะ failWhenNameIsNull () {
        บุคคล บุคคล = คนใหม่ (null, 20, ที่อยู่ใหม่(...), ...);

        assertThatThrownBy(() -> validator.validate(บุคคล))
            .isInstanceOf(InvalidPersonException.class);
    }

    @ทดสอบ
    เป็นโมฆะ failWhenAgeIsNegative () {
        บุคคล บุคคล = คนใหม่ ("จอห์น", -5, ที่อยู่ใหม่ (...), ...);

        assertThatThrownBy(() -> validator.validate(บุคคล))
            .isInstanceOf(InvalidPersonException.class);

    }
}

รูปแบบการออกแบบ ObjectMother มักใช้ในการทดสอบที่สร้างวัตถุที่ซับซ้อนเพื่อซ่อนรายละเอียดการสร้างอินสแตนซ์จากการทดสอบ การทดสอบหลายรายการอาจสร้างวัตถุเดียวกันแต่ทดสอบสิ่งต่าง ๆ กับวัตถุนั้น

การทดสอบ #1 นั้นคล้ายกับการทดสอบ #2 มาก เราสามารถ refactor PersonValidatorShould ได้โดยการแยกการตรวจสอบเป็นวิธีการส่วนตัว จากนั้นจึงส่งต่ออินสแตนซ์ Person ผิดกฎหมายไปให้ โดยคาดว่าทั้งหมดจะล้มเหลวในลักษณะเดียวกัน

 คลาส PersonValidatorShould {

     เครื่องมือตรวจสอบ PersonalValidator ส่วนตัว = PersonValidator ใหม่ ();

     @ทดสอบ
     เป็นโมฆะ failWhenNameIsNull () {
         ควรFailValidation(PersonObjectMother.createPersonWithoutName());
     }

     @ทดสอบ
     เป็นโมฆะ failWhenAgeIsNegative () {
         ควรFailValidation(PersonObjectMother.createPersonWithNegativeAge());
     }

     โมฆะส่วนตัวควรFailValidation (บุคคลไม่ถูกต้อง) {
         assertThatThrownBy(() -> validator.validate(invalidPerson))
             .isInstanceOf(InvalidPersonException.class);
   
     }
 }

สุ่มทดสอบ

เราควรทดสอบการสุ่มในโค้ดของเราอย่างไร?

สมมติว่าเรามี PersonGenerator ที่ generateRandom Random เพื่อสร้างอินสแตนซ์ Person แบบสุ่ม

เราเริ่มต้นด้วยการเขียนต่อไปนี้:

 คลาส PersonGeneratorShould {

     เครื่องกำเนิด PersonGenerator ส่วนตัว = PersonGenerator ใหม่ ();

     @ทดสอบ
     เป็นโมฆะ createValidPerson () {
         บุคคล บุคคล = generator.generateRandom();
         ยืนยันว่า(คน).
    }
}

แล้วเราควรถามตัวเองว่า

  • ฉันกำลังพยายามพิสูจน์อะไรที่นี่ ฟังก์ชันนี้ต้องทำอะไร?
  • ฉันควรตรวจสอบว่าบุคคลที่สร้างขึ้นนั้นเป็นอินสแตนซ์ที่ไม่ใช่โมฆะหรือไม่
  • ฉันต้องพิสูจน์ว่าเป็นการสุ่มหรือไม่?
  • อินสแตนซ์ที่สร้างขึ้นต้องปฏิบัติตามกฎเกณฑ์ทางธุรกิจบางอย่างหรือไม่

เราสามารถทำให้การทดสอบของเราง่ายขึ้นโดยใช้ Dependency Injection

 อินเทอร์เฟซสาธารณะ RandomGenerator {
     สตริง generateRandomString ();
     int สร้าง RandomInteger();
}

ขณะนี้ PersonGenerator มีตัวสร้างอื่นที่ยอมรับอินสแตนซ์ของอินเทอร์เฟซนั้นด้วย โดยค่าเริ่มต้น จะใช้ JavaRandomGenerator ที่สร้างค่าสุ่มโดยใช้ java.Random

อย่างไรก็ตาม ในการทดสอบ เราสามารถเขียนการใช้งานอื่นที่สามารถคาดเดาได้มากกว่านี้

 @ทดสอบ
 เป็นโมฆะ createValidPerson () {
     RandomGenerator randomGenerator = PredictableGenerator ใหม่ ("John Doe", 20);
     เครื่องกำเนิด PersonGenerator = PersonGenerator ใหม่ (randomGenerator);
     บุคคล บุคคล = generator.generateRandom();
     assertThat(person).isEqualTo(คนใหม่("John Doe", 20));
}

การทดสอบนี้พิสูจน์ว่า PersonGenerator สร้างอินสแตนซ์แบบสุ่มตามที่ระบุโดย RandomGenerator โดยไม่ต้องเข้าไปดูรายละเอียดใดๆ ของ RandomGenerator

การทดสอบ JavaRandomGenerator ไม่ได้เพิ่มค่าใด ๆ เนื่องจากเป็น wrapper แบบง่าย ๆ รอบ ๆ java.Random โดยการทดสอบ คุณจะต้องทดสอบ java.Random จากไลบรารีมาตรฐาน Java การเขียนการทดสอบที่ชัดเจนจะนำไปสู่การบำรุงรักษาเพิ่มเติมโดยมีประโยชน์เพียงเล็กน้อยเท่านั้น

เพื่อหลีกเลี่ยงการเขียนการใช้งานเพื่อวัตถุประสงค์ในการทดสอบ เช่น PredictableGenerator คุณควรใช้ไลบรารีจำลอง เช่น Mockito

เมื่อเราเขียน PredictableGenerator จริง ๆ แล้วเราได้ stubbed คลาส RandomGenerator ด้วยตนเอง คุณยังสามารถใช้ Mockito ได้:

 @ทดสอบ
 เป็นโมฆะ createValidPerson () {
     RandomGenerator randomGenerator = เยาะเย้ย (RandomGenerator.class);
     เมื่อ (randomGenerator.generateRandomString()).thenReturn("John Doe");
     เมื่อ (randomGenerator.generateRandomInteger()).thenReturn(20);

     เครื่องกำเนิด PersonGenerator = PersonGenerator ใหม่ (randomGenerator);
     บุคคล บุคคล = generator.generateRandom();
     assertThat(person).isEqualTo(คนใหม่("John Doe", 20));
 }

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

Mockito เป็นไลบรารี Java สำหรับเขียน mocks และ stubs มีประโยชน์มากเมื่อทำการทดสอบโค้ดที่ขึ้นอยู่กับไลบรารีภายนอกที่คุณไม่สามารถสร้างอินสแตนซ์ได้อย่างง่ายดาย ช่วยให้คุณสามารถเขียนพฤติกรรมสำหรับคลาสเหล่านี้โดยไม่ต้องดำเนินการโดยตรง

Mockito ยังอนุญาตให้ใช้รูปแบบอื่นสำหรับการสร้างและฉีด mocks เพื่อลดต้นแบบเมื่อเรามีการทดสอบมากกว่าหนึ่งแบบที่คล้ายกับที่เราคุ้นเคย:

 @ExtendWith(MockitoExtension.class) // 1
 คลาส PersonGeneratorShould {

     @Mock // 2
     RandomGenerator สุ่มสร้าง;

     @InjectMocks // 3
     เครื่องกำเนิด PersonGenerator ส่วนตัว;

     @ทดสอบ
     เป็นโมฆะ createValidPerson () {
         เมื่อ (randomGenerator.generateRandomString()).thenReturn("John Doe");
         เมื่อ (randomGenerator.generateRandomInteger()).thenReturn(20);

         บุคคล บุคคล = generator.generateRandom();
         assertThat(person).isEqualTo(คนใหม่("John Doe", 20));
     }
}

1. JUnit 5 สามารถใช้ “ส่วนขยาย” เพื่อขยายขีดความสามารถ คำอธิบายประกอบนี้ช่วยให้สามารถจดจำการเยาะเย้ยผ่านคำอธิบายประกอบและแทรกคำอธิบายประกอบได้อย่างถูกต้อง

2. คำอธิบายประกอบ @Mock สร้างตัวอย่างจำลองของฟิลด์ สิ่งนี้เหมือนกับการเขียน mock(RandomGenerator.class) ในเนื้อหาวิธีการทดสอบของเรา

3. คำอธิบายประกอบ @InjectMocks จะสร้างอินสแตนซ์ใหม่ของ PersonGenerator และฉีด mocks ในอินสแตนซ์ตัว generator

สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับส่วนขยาย JUnit 5 ดูที่นี่

สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับการฉีด Mockito โปรดดูที่นี่

มีข้อผิดพลาดประการหนึ่งในการใช้ @InjectMocks อาจทำให้ไม่จำเป็นต้องประกาศอินสแตนซ์ของอ็อบเจ็กต์ด้วยตนเอง แต่เราสูญเสียความปลอดภัยในการคอมไพล์เวลาของคอนสตรัคเตอร์ หาก ณ เวลาใดมีคนเพิ่มการพึ่งพาอื่นให้กับ Constructor เราจะไม่ได้รับข้อผิดพลาดในการคอมไพล์ที่นี่ ซึ่งอาจนำไปสู่การทดสอบที่ล้มเหลวซึ่งตรวจจับได้ยาก ฉันชอบใช้ @BeforeEach เพื่อตั้งค่าอินสแตนซ์ด้วยตนเอง:

 @ExtendWith(MockitoExtension.class)
คลาส PersonGeneratorShould {

     @Mock
     RandomGenerator สุ่มสร้าง;

     เครื่องกำเนิด PersonGenerator ส่วนตัว;

     @BeforeEach
     เป็นโมฆะการตั้งค่า () {
         เครื่องกำเนิด = PersonGenerator ใหม่ (randomGenerator);
     }

     @ทดสอบ
     เป็นโมฆะ createValidPerson () {
         เมื่อ (randomGenerator.generateRandomString()).thenReturn("John Doe");
         เมื่อ (randomGenerator.generateRandomInteger()).thenReturn(20);

         บุคคล บุคคล = generator.generateRandom();
         assertThat(person).isEqualTo(คนใหม่("John Doe", 20));
     }
}

การทดสอบกระบวนการที่ไวต่อเวลา

ส่วนหนึ่งของรหัสมักจะขึ้นอยู่กับการประทับเวลา และเรามักจะใช้วิธีเช่น System.currentTimeMillis() เพื่อรับการประทับเวลาของยุคปัจจุบัน

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

 คลาส IndexerShould {
     ตัวสร้างดัชนีส่วนตัว = ตัวสร้างดัชนีใหม่ ();
     @ทดสอบ
     เป็นโมฆะ generateIndexNameForTomorrow () {
         String indexName = indexer.tomorrow("my-index");
         // การทดสอบนี้ใช้ได้วันนี้ แต่พรุ่งนี้ล่ะ
        ยืนยันนั่น (indexName)
           .isEqualTo("my-index.2022-02-02");
     }
}

เราควรใช้ Dependency Injection อีกครั้งเพื่อให้สามารถ 'ควบคุม' ว่าวันนี้เป็นวันใดเมื่อสร้างชื่อดัชนี

Java มีคลาส Clock เพื่อจัดการกรณีการใช้งานเช่นนี้ เราสามารถส่งตัวอย่าง Clock ไปยังตัวสร้าง Indexer เพื่อควบคุมเวลาได้ ตัวสร้างเริ่มต้นสามารถใช้ Clock.systemUTC() เพื่อความเข้ากันได้แบบย้อนหลัง ขณะนี้เราสามารถแทนที่การเรียก System.currentTimeMillis() ด้วย clock.millis()

การฉีด Clock ทำให้เราบังคับใช้เวลาที่คาดการณ์ได้ในชั้นเรียนของเราและเขียนการทดสอบได้ดีขึ้น

การทดสอบวิธีการผลิตไฟล์

  • เราควรทดสอบคลาสที่เขียนผลลัพธ์ไปยังไฟล์อย่างไร?
  • เราควรเก็บไฟล์เหล่านี้ไว้ที่ใดเพื่อให้ทำงานบนระบบปฏิบัติการใด ๆ
  • เราจะแน่ใจได้อย่างไรว่าไฟล์นั้นไม่มีอยู่แล้ว?

เมื่อต้องจัดการกับไฟล์ การเขียนการทดสอบอาจเป็นเรื่องยากหากเราพยายามจัดการกับข้อกังวลเหล่านี้ด้วยตนเอง ดังที่เราจะเห็นในตัวอย่างต่อไปนี้ การทดสอบที่ตามมาคือการทดสอบแบบเก่าที่มีคุณภาพน่าสงสัย ควรทดสอบว่า DogToCsvWriter อนุกรมและเขียนสุนัขลงในไฟล์ CSV หรือไม่:

 คลาส DogToCsvWriterShould {

     ผู้เขียน DogToCsvWriter ส่วนตัว = DogToCsvWriter ใหม่ ("/tmp/dogs.csv");
     
     @ทดสอบ
     เป็นโมฆะ convertToCsv () {
         writer.appendAsCsv (สุนัขใหม่ (Breed.CORGI, Color.BROWN, "Monty"));
         writer.appendAsCsv (สุนัขใหม่ (Breed.MALTESE, Color.WHITE, "Zoe"));

         สตริง csv = Files.readString("/tmp/dogs.csv");

         assertThat(csv).isEqualTo("Monty,corgi,brown\nZoe,maltese,white");
     }
}

กระบวนการซีเรียลไลซ์เซชั่นควรแยกออกจากกระบวนการเขียน แต่มาเน้นที่การแก้ไขการทดสอบกัน

ปัญหาแรกของการทดสอบข้างต้นคือใช้งานไม่ได้บน Windows เนื่องจากผู้ใช้ Windows จะไม่สามารถแก้ไขเส้นทางได้ /tmp/dogs.csv อีกปัญหาหนึ่งคือ มันจะไม่ทำงานหากไฟล์นั้นมีอยู่แล้ว เนื่องจากไฟล์จะไม่ถูกลบเมื่อการทดสอบด้านบนดำเนินการ อาจทำงานได้ดีในไปป์ไลน์ CI/CD แต่จะไม่ทำงานในเครื่องหากเรียกใช้หลายครั้ง

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

 คลาส DogToCsvWriterShould {

     @ทดสอบ
     เป็นโมฆะ convertToCsv(@TempDir เส้นทาง tempDir) {
         เส้นทาง dogCsv = tempDir.resolve("dogs.csv");
         นักเขียน DogToCsvWriter = DogToCsvWriter ใหม่ (dogsCsv);
         writer.appendAsCsv (สุนัขใหม่ (Breed.CORGI, Color.BROWN, "Monty"));
         writer.appendAsCsv (สุนัขใหม่ (Breed.MALTESE, Color.WHITE, "Zoe"));

         สตริง csv = Files.readString (dogsCsv);

         assertThat(csv).isEqualTo("Monty,corgi,brown\nZoe,maltese,white");
     }
}

ด้วยการเปลี่ยนแปลงเล็กๆ น้อยๆ นี้ เรามั่นใจว่าการทดสอบข้างต้นจะใช้ได้กับ Windows, macOS และ Linux โดยไม่ต้องกังวลกับเส้นทางที่แน่นอน นอกจากนี้ยังจะลบไฟล์ที่สร้างขึ้นหลังการทดสอบเพื่อให้เราสามารถเรียกใช้ได้หลายครั้งและรับผลลัพธ์ที่คาดเดาได้ในแต่ละครั้ง

คำสั่งเทียบกับการทดสอบข้อความค้นหา

คำสั่งและแบบสอบถามต่างกันอย่างไร

  • คำสั่ง : เราสั่งให้อ็อบเจ็กต์ดำเนินการสร้างเอฟเฟกต์โดยไม่คืนค่า (วิธีเป็นโมฆะ)
  • แบบสอบถาม : เราขอให้วัตถุดำเนินการและส่งกลับผลลัพธ์หรือข้อยกเว้น

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

การยืนยันที่เราเขียนจนถึงตอนนี้สำหรับข้อความค้นหาเริ่มต้นด้วย assertThat เมื่อเขียนการทดสอบคำสั่ง เราใช้ชุดของเมธอดที่แตกต่างกัน เนื่องจากเราไม่ได้ตรวจสอบผลลัพธ์โดยตรงของเมธอดเหมือนกับที่เราทำกับคิวรีอีกต่อไป เราต้องการ 'ยืนยัน' การโต้ตอบที่วิธีการของเรามีกับส่วนอื่น ๆ ของระบบของเรา

 @ExtendWith(MockitoExtension.class)
 คลาส FeedMentionServiceShould {

     @Mock
     ที่เก็บ FeedRepository ส่วนตัว

     @Mock
     ตัวปล่อย FeedMentionEventEmitter ส่วนตัว;

     บริการ FeedMentionService ส่วนตัว;

     @BeforeEach
     เป็นโมฆะการตั้งค่า () {
         บริการ = FeedMentionService ใหม่ (ที่เก็บ, emitter);
     }

     @ทดสอบ
     ถือเป็นโมฆะ insertMentionToFeed () {
         feedId ยาว = 1L;
         กล่าวถึง = ...;

         เมื่อ (repository.upsertMention(feedId, กล่าวถึง))
             .thenReturn(UpsertResult.success(feedId, กล่าวถึง));

         เหตุการณ์ FeedInsertionEvent = FeedInsertionEvent ใหม่ (feedId กล่าวถึง);
         กล่าวถึงService.insertMentionToFeed(เหตุการณ์);

         ตรวจสอบ (emitter).mentionInsertedToFeed (feedId กล่าวถึง);
         VerifyNoMoreInteractions(อีซีแอล);
     }
}

ในการทดสอบนี้ ก่อนอื่นเราเยาะเย้ยที่เก็บของเราเพื่อตอบกลับด้วย UpsertResult.success เมื่อถูกขอให้เพิ่มการกล่าวถึงในฟีดของเรา เราไม่เกี่ยวข้องกับการทดสอบพื้นที่เก็บข้อมูลที่นี่ ควรทดสอบวิธีการเก็บข้อมูลใน FeedRepositoryShould การเยาะเย้ยพฤติกรรมนี้ เราไม่ได้เรียกวิธีการเก็บข้อมูลจริงๆ เราแค่บอกวิธีการตอบสนองในครั้งต่อไปที่มีการโทร

จากนั้นเรา mentionService การกล่าวถึงบริการให้แทรกการกล่าวถึงนี้ในฟีดของเรา เรารู้ว่าควรปล่อยผลลัพธ์ก็ต่อเมื่อแทรกการกล่าวถึงในฟีดสำเร็จเท่านั้น เมื่อใช้วิธีการ verify เราจะสามารถมั่นใจได้ว่ามีการเรียกเมธอด mentionInsertedToFeed ด้วยการกล่าวถึงและฟีดของเรา และจะไม่ถูกเรียกอีกโดยใช้ verifyNoMoreInteractions

ความคิดสุดท้าย

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

สนใจเข้าร่วมทีม Mediatoolkit หรือไม่?
ตรวจสอบตำแหน่งที่เปิดของเราสำหรับ Senior Frontend Developer !