การทดสอบตามตัวอย่าง
เผยแพร่แล้ว: 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 !
