1. Gambaran keseluruhan
Perpustakaan JUnit 5 menawarkan banyak ciri baru berbanding versi sebelumnya. Salah satu ciri tersebut ialah templat ujian. Singkatnya, templat ujian adalah generalisasi yang kuat dari ujian parameter dan berulang JUnit 5.
Dalam tutorial ini, kita akan belajar bagaimana membuat templat ujian menggunakan JUnit 5.
2. Pergantungan Maven
Mari mulakan dengan menambahkan kebergantungan ke pom.xml kami .
Kita perlu menambahkan kebergantungan mesin JUnit 5 junit-jupiter utama :
org.junit.jupiter junit-jupiter-engine 5.7.0
Selain itu, kita juga perlu menambahkan kebergantungan junit-jupiter-api :
org.junit.jupiter junit-jupiter-api 5.7.0
Begitu juga, kita boleh menambahkan kebergantungan yang diperlukan pada fail build.gradle kami :
testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.7.0' testCompile group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.7.0'
3. Penyataan Masalah
Sebelum melihat templat ujian, mari kita lihat secara ringkas ujian parameter JUnit 5. Uji parameter membolehkan kita memasukkan parameter yang berbeza ke dalam kaedah ujian. Akibatnya, ketika menggunakan pengujian parameter, kita dapat menjalankan metode uji tunggal berkali-kali dengan parameter yang berlainan.
Mari kita anggap bahawa sekarang kita ingin menjalankan kaedah ujian kita berulang kali - bukan hanya dengan parameter yang berbeza, tetapi juga dalam konteks permintaan yang berbeza setiap kali.
Dengan kata lain, kami ingin kaedah ujian dijalankan berkali-kali, dengan setiap permintaan menggunakan kombinasi konfigurasi yang berbeza seperti:
- menggunakan parameter yang berbeza
- menyediakan contoh kelas ujian secara berbeza - iaitu, menyuntikkan kebergantungan yang berbeza ke dalam contoh ujian
- menjalankan ujian dalam keadaan yang berbeza, seperti mengaktifkan / menonaktifkan subkumpulan panggilan jika persekitarannya adalah " QA "
- berjalan dengan tingkah laku panggilan balik kitaran hidup yang berbeza - mungkin kita ingin menyiapkan dan merobohkan pangkalan data sebelum dan selepas subkumpulan panggilan
Menggunakan ujian parameter dengan cepat terbukti terhad dalam kes ini. Syukurlah, JUnit 5 menawarkan penyelesaian yang kuat untuk senario ini dalam bentuk templat ujian.
4. Templat Ujian
Templat ujian itu sendiri bukan kes ujian. Sebaliknya, seperti namanya, mereka hanyalah templat untuk kes ujian yang diberikan. Mereka adalah generalisasi yang kuat dari ujian parameter dan berulang.
Templat ujian dipanggil sekali untuk setiap konteks permintaan yang diberikan kepada mereka oleh penyedia konteks permintaan.
Sekarang mari kita lihat contoh templat ujian. Seperti yang telah kita ketahui di atas, pelakon utama adalah:
- kaedah sasaran ujian
- kaedah templat ujian
- satu atau lebih penyedia konteks permintaan yang didaftarkan dengan kaedah templat
- satu atau lebih konteks permintaan yang disediakan oleh setiap penyedia konteks permintaan
4.1. Kaedah Sasaran Ujian
Untuk contoh ini, kami akan menggunakan kaedah UserIdGeneratorImpl.generate yang mudah sebagai sasaran ujian kami.
Mari tentukan kelas UserIdGeneratorImpl :
public class UserIdGeneratorImpl implements UserIdGenerator { private boolean isFeatureEnabled; public UserIdGeneratorImpl(boolean isFeatureEnabled) { this.isFeatureEnabled = isFeatureEnabled; } public String generate(String firstName, String lastName) { String initialAndLastName = firstName.substring(0, 1).concat(lastName); return isFeatureEnabled ? "bael".concat(initialAndLastName) : initialAndLastName; } }
Yang menjana kaedah, yang merupakan sasaran ujian kami, mengambil firstName dan LASTNAME sebagai parameter dan menjana id pengguna. Format id pengguna berbeza-beza, bergantung pada apakah suis ciri diaktifkan atau tidak.
Mari lihat bagaimana ini:
Given feature switch is disabled When firstName = "John" and lastName = "Smith" Then "JSmith" is returned Given feature switch is enabled When firstName = "John" and lastName = "Smith" Then "baelJSmith" is returned
Seterusnya, mari tulis kaedah templat ujian.
4.2. Kaedah Templat Ujian
Berikut adalah templat ujian untuk kaedah sasaran ujian kami UserIdGeneratorImpl.generate :
public class UserIdGeneratorImplUnitTest { @TestTemplate @ExtendWith(UserIdGeneratorTestInvocationContextProvider.class) public void whenUserIdRequested_thenUserIdIsReturnedInCorrectFormat(UserIdGeneratorTestCase testCase) { UserIdGenerator userIdGenerator = new UserIdGeneratorImpl(testCase.isFeatureEnabled()); String actualUserId = userIdGenerator.generate(testCase.getFirstName(), testCase.getLastName()); assertThat(actualUserId).isEqualTo(testCase.getExpectedUserId()); } }
Mari kita perhatikan kaedah templat ujian dengan lebih dekat.
Sebagai permulaan, kami membuat kaedah templat ujian kami dengan menandainya dengan anotasi JUnit 5 @TestTemplate .
Berikutan itu, kami mendaftarkan penyedia konteks , UserIdGeneratorTestInvocationContextProvider, menggunakan anotasi @ExtendWith . Kami boleh mendaftarkan beberapa penyedia konteks dengan templat ujian. Walau bagaimanapun, untuk tujuan contoh ini, kami mendaftar penyedia tunggal.
Juga, kaedah templat menerima contoh UserIdGeneratorTestCase sebagai parameter. Ini hanyalah kelas pembungkus untuk input dan hasil yang diharapkan dari kes ujian:
public class UserIdGeneratorTestCase { private boolean isFeatureEnabled; private String firstName; private String lastName; private String expectedUserId; // Standard setters and getters }
Akhirnya, kami menggunakan kaedah sasaran ujian dan menegaskan bahawa hasilnya adalah seperti yang diharapkan
Kini, sudah tiba masa untuk menentukan pembekal doa konteks kita .
4.3. Penyedia Konteks Permintaan
Kita perlu mendaftar sekurang-kurangnya satu TestTemplateInvocationContextProvider dengan templat ujian kami. Setiap berdaftar TestTemplateInvocationContextProvider menyediakan Stream of TestTemplateInvocationContext keadaan .
Previously, using the @ExtendWith annotation, we registered UserIdGeneratorTestInvocationContextProvider as our invocation provider.
Let's define this class now:
public class UserIdGeneratorTestInvocationContextProvider implements TestTemplateInvocationContextProvider { //... }
Our invocation context implements the TestTemplateInvocationContextProvider interface, which has two methods:
- supportsTestTemplate
- provideTestTemplateInvocationContexts
Let's start by implementing the supportsTestTemplate method:
@Override public boolean supportsTestTemplate(ExtensionContext extensionContext) { return true; }
The JUnit 5 execution engine calls the supportsTestTemplate method first to validate if the provider is applicable for the given ExecutionContext. In this case, we simply return true.
Now, let's implement the provideTestTemplateInvocationContexts method:
@Override public Stream provideTestTemplateInvocationContexts( ExtensionContext extensionContext) { boolean featureDisabled = false; boolean featureEnabled = true; return Stream.of( featureDisabledContext( new UserIdGeneratorTestCase( "Given feature switch disabled When user name is John Smith Then generated userid is JSmith", featureDisabled, "John", "Smith", "JSmith")), featureEnabledContext( new UserIdGeneratorTestCase( "Given feature switch enabled When user name is John Smith Then generated userid is baelJSmith", featureEnabled, "John", "Smith", "baelJSmith")) ); }
The purpose of the provideTestTemplateInvocationContexts method is to provide a Stream of TestTemplateInvocationContext instances. For our example, it returns two instances, provided by the methods featureDisabledContext and featureEnabledContext. Consequently, our test template will run twice.
Next, let's look at the two TestTemplateInvocationContext instances returned by these methods.
4.4. The Invocation Context Instances
The invocation contexts are implementations of the TestTemplateInvocationContext interface and implement the following methods:
- getDisplayName – provide a test display name
- getAdditionalExtensions – return additional extensions for the invocation context
Let's define the featureDisabledContext method that returns our first invocation context instance:
private TestTemplateInvocationContext featureDisabledContext( UserIdGeneratorTestCase userIdGeneratorTestCase) { return new TestTemplateInvocationContext() { @Override public String getDisplayName(int invocationIndex) { return userIdGeneratorTestCase.getDisplayName(); } @Override public List getAdditionalExtensions() { return asList( new GenericTypedParameterResolver(userIdGeneratorTestCase), new BeforeTestExecutionCallback() { @Override public void beforeTestExecution(ExtensionContext extensionContext) { System.out.println("BeforeTestExecutionCallback:Disabled context"); } }, new AfterTestExecutionCallback() { @Override public void afterTestExecution(ExtensionContext extensionContext) { System.out.println("AfterTestExecutionCallback:Disabled context"); } } ); } }; }
Firstly, for the invocation context returned by the featureDisabledContext method, the extensions that we register are:
- GenericTypedParameterResolver – a parameter resolver extension
- BeforeTestExecutionCallback – a lifecycle callback extension that runs immediately before the test execution
- AfterTestExecutionCallback – a lifecycle callback extension that runs immediately after the test execution
However, for the second invocation context, returned by the featureEnabledContext method, let's register a different set of extensions (keeping the GenericTypedParameterResolver):
private TestTemplateInvocationContext featureEnabledContext( UserIdGeneratorTestCase userIdGeneratorTestCase) { return new TestTemplateInvocationContext() { @Override public String getDisplayName(int invocationIndex) { return userIdGeneratorTestCase.getDisplayName(); } @Override public List getAdditionalExtensions() { return asList( new GenericTypedParameterResolver(userIdGeneratorTestCase), new DisabledOnQAEnvironmentExtension(), new BeforeEachCallback() { @Override public void beforeEach(ExtensionContext extensionContext) { System.out.println("BeforeEachCallback:Enabled context"); } }, new AfterEachCallback() { @Override public void afterEach(ExtensionContext extensionContext) { System.out.println("AfterEachCallback:Enabled context"); } } ); } }; }
For the second invocation context, the extensions that we register are:
- GenericTypedParameterResolver – a parameter resolver extension
- DisabledOnQAEnvironmentExtension – an execution condition to disable the test if the environment property (loaded from the application.properties file) is “qa“
- BeforeEachCallback – a lifecycle callback extension that runs before each test method execution
- AfterEachCallback – a lifecycle callback extension that runs after each test method execution
From the above example, it is clear to see that:
- the same test method is run under multiple invocation contexts
- each invocation context uses its own set of extensions that differ both in number and nature from the extensions in other invocation contexts
As a result, a test method can be invoked multiple times under a completely different invocation context each time. And by registering multiple context providers, we can provide even more additional layers of invocation contexts under which to run the test.
5. Conclusion
In this article, we looked at how JUnit 5's test templates are a powerful generalization of parameterized and repeated tests.
Sebagai permulaan, kami melihat beberapa batasan ujian parameter. Seterusnya, kami membincangkan bagaimana templat ujian mengatasi batasan dengan membenarkan ujian dijalankan dalam konteks yang berbeza untuk setiap permintaan.
Akhirnya, kami melihat contoh membuat templat ujian baru. Kami membongkar contohnya untuk memahami bagaimana templat berfungsi bersama dengan penyedia konteks permintaan dan konteks permintaan
Seperti biasa, kod sumber untuk contoh yang digunakan dalam artikel ini terdapat di GitHub.