การทดสอบตามตัวอย่าง
เผยแพร่แล้ว: 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
ในทุกชื่อเมธอด คุณสามารถอ่านเพิ่มเติมเกี่ยวกับเรื่องนี้ได้ที่นี่
การทดสอบข้างต้นทำอะไร?
มาแบ่งบรรทัดทดสอบทีละบรรทัด:
- คำอธิบายประกอบ
@Test
ช่วยให้เฟรมเวิร์ก JUnit รู้ว่าเมธอดใดที่ควรรันเป็นแบบทดสอบ เป็นเรื่องปกติอย่างยิ่งที่จะมีเมธอดprivate
ในคลาสการทดสอบซึ่งไม่ใช่การทดสอบ - นี่คือขั้นตอนการ จัดการ ทดสอบของเรา ซึ่งเราเตรียมสภาพแวดล้อมการทดสอบ ทั้งหมดที่เราต้องการสำหรับการทดสอบนี้คือมีอินสแตนซ์ของ
Calculator
- นี่คือขั้นตอน การดำเนิน การที่เราเรียกพฤติกรรมที่เราต้องการทดสอบ
- นี่คือระยะ ยืนยัน ซึ่งเราตรวจสอบสิ่งที่เกิดขึ้นและหากทุกอย่างได้รับการแก้ไขตามที่คาดไว้
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 !