JUnit 5 eğitimi, bölüm 1: JUnit 5, Mockito ve Hamcrest ile birim testi

JUnit 5, Java'da birim testleri geliştirmek için yeni fiili standarttır. Bu en yeni sürüm, Java 5'in kısıtlamalarını geride bıraktı ve Java 8'in birçok özelliğini entegre etti, en önemlisi lambda ifadelerini destekliyor.

JUnit 5'e iki bölümlük bir girişin bu ilk yarısında, JUnit 5 ile test etmeye başlayacaksınız. JUnit 5'i kullanmak için bir Maven projesini nasıl yapılandıracağınızı, @Testve @ParameterizedTestek açıklamalarını kullanarak testlerin nasıl yazılacağını göstereceğim. ve JUnit 5'te yeni yaşam döngüsü ek açıklamalarıyla nasıl çalışılacağı. Ayrıca filtre etiketlerinin kullanımına ilişkin kısa bir örnek de göreceksiniz ve size JUnit 5'i üçüncü taraf ispat kitaplığı ile nasıl entegre edeceğinizi göstereceğim - bu durumda Hamcrest . Son olarak, karmaşık, gerçek dünya sistemleri için daha sağlam birim testleri yazabilmeniz için JUnit 5'i Mockito ile entegre etmeye yönelik hızlı, öğretici bir giriş alacaksınız.

indir Kodu alın Bu eğitimdeki örnekler için kaynak kodunu alın. Steven Haines tarafından JavaWorld için oluşturuldu.

Test odaklı geliştirme

Herhangi bir süredir Java kodu geliştiriyorsanız, muhtemelen test odaklı geliştirmeye yakından aşinasınızdır, bu yüzden bu bölümü kısa tutacağım. Bununla birlikte, neden birim testleri yazdığımızı ve geliştiricilerin birim testleri tasarlarken kullandıkları stratejileri anlamak önemlidir .

Test odaklı geliştirme (TDD); kodlama, test ve tasarımı iç içe geçiren bir yazılım geliştirme sürecidir. Uygulamalarınızın kalitesini artırmayı amaçlayan test öncelikli bir yaklaşımdır. Test odaklı geliştirme, aşağıdaki yaşam döngüsü ile tanımlanır:

  1. Bir test ekleyin.
  2. Tüm testlerinizi çalıştırın ve yeni testin başarısız olduğunu gözlemleyin.
  3. Kodu uygulayın.
  4. Tüm testlerinizi çalıştırın ve yeni testin başarılı olduğunu gözlemleyin.
  5. Kodu yeniden düzenleyin.

Şekil 1, bu TDD yaşam döngüsünü göstermektedir.

Steven Haines

Kodunuzu yazmadan önce test yazmanın iki yönlü bir amacı vardır. Birincisi, çözmeye çalıştığınız iş problemini düşünmeye zorlar. Örneğin, başarılı senaryolar nasıl davranmalı? Hangi koşullar başarısız olmalı? Nasıl başarısız olurlar? İkincisi, önce test etmek size testlerinizde daha fazla güven verir. Kodu yazdıktan sonra ne zaman testler yazsam, gerçekten hataları yakaladıklarından emin olmak için her zaman onları kırmak zorundayım. Testleri yazmak önce bu ekstra adımı önler.

Mutlu yol için testler yazmak genellikle kolaydır: İyi girdi verildiğinde, sınıf belirleyici bir yanıt vermelidir. Ancak, özellikle karmaşık bileşenler için olumsuz (veya başarısız) test senaryoları yazmak daha karmaşık olabilir.

Örnek olarak, bir veritabanı deposu için testler yazmayı düşünün. Mutlu yolda, veritabanına bir kayıt ekleriz ve oluşturulan anahtarlar da dahil olmak üzere oluşturulan nesneyi geri alırız. Gerçekte, zaten başka bir kayıt tarafından tutulan benzersiz bir sütun değerine sahip bir kayıt eklemek gibi bir çakışma olasılığını da göz önünde bulundurmalıyız. Ek olarak, arşiv veri tabanına bağlanamadığında, belki de kullanıcı adı veya şifre değiştiği için ne olur? Aktarım sırasında bir ağ hatası varsa ne olur? Talep, tanımladığınız zaman aşımı sınırında tamamlanmazsa ne olur?

Sağlam bir bileşen oluşturmak için, tüm olası ve olası olmayan senaryoları göz önünde bulundurmanız, bunlar için testler geliştirmeniz ve bu testleri tatmin edecek kodunuzu yazmanız gerekir. Makalenin ilerleyen kısımlarında, JUnit 5'teki bu senaryoları test etmenize yardımcı olabilecek bazı yeni özelliklerin yanı sıra farklı hata senaryoları oluşturmaya yönelik stratejilere bakacağız.

JUnit 5'i benimsemek

JUnit'i bir süredir kullanıyorsanız, JUnit 5'teki bazı değişiklikler bir ayarlama olacaktır. İşte iki sürüm arasında nelerin farklı olduğuna dair üst düzey bir özet:

  • JUnit 5 artık org.junit.jupitergrup içinde paketlenmiştir ve bu , onu Maven ve Gradle projelerinize nasıl dahil edeceğinizi değiştirir.
  • JUnit 4, minimum JDK JDK 5 gerektiriyordu; JUnit 5, minimum JDK 8 gerektirir.
  • JUnit 4 en @Before, @BeforeClass, @After, ve @AfterClassek açıklamalar yerini almış @BeforeEach, @BeforeAll, @AfterEach, ve @AfterAllsırasıyla.
  • JUnit 4'ün @Ignoreaçıklamasının yerini @Disabledaçıklama aldı.
  • @CategoryEk açıklama ile değiştirildi @Tagaçıklama.
  • JUnit 5, yeni bir dizi onaylama yöntemi ekler.
  • Koşucular, uzantı uygulayıcıları için yeni bir API ile değiştirildi.
  • JUnit 5, bir testin yürütülmesini durduran varsayımlar sunar.
  • JUnit 5, yuvalanmış ve dinamik test sınıflarını destekler.

Bu yeni özelliklerin çoğunu bu makalede inceleyeceğiz.

JUnit 5 ile birim testi

Bir projeyi birim testi için JUnit 5 kullanacak şekilde yapılandırmanın uçtan uca bir örneğiyle basit bir şekilde başlayalım. Liste 1, MathToolsyöntemi bir pay ve paydayı a'ya dönüştüren bir sınıfı gösterir double.

Liste 1. Örnek bir JUnit 5 projesi (MathTools.java)

 package com.javaworld.geekcap.math; public class MathTools { public static double convertToDecimal(int numerator, int denominator) { if (denominator == 0) { throw new IllegalArgumentException("Denominator must not be 0"); } return (double)numerator / (double)denominator; } }

MathToolsSınıfı ve yöntemini test etmek için iki temel senaryomuz var :

  • Bir geçerli bir test biz pay ve payda sıfır olmayan tamsayılar geçmesi ettiği,.
  • Bir arıza senaryosu , burada biz payda sıfır değerini geçmektedir.

Liste 2, bu iki senaryoyu test etmek için bir JUnit 5 test sınıfını gösterir.

Liste 2. Bir JUnit 5 test sınıfı (MathToolsTest.java)

 package com.javaworld.geekcap.math; import java.lang.IllegalArgumentException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class MathToolsTest { @Test void testConvertToDecimalSuccess() { double result = MathTools.convertToDecimal(3, 4); Assertions.assertEquals(0.75, result); } @Test void testConvertToDecimalInvalidDenominator() { Assertions.assertThrows(IllegalArgumentException.class, () -> MathTools.convertToDecimal(3, 0)); } }

Liste 2'de testConvertToDecimalInvalidDenominatoryöntem, MathTools::convertToDecimalyöntemi bir assertThrowsçağrı içinde yürütür . İlk argüman, atılması beklenen istisna türüdür. İkinci argüman, bu istisnayı atacak bir işlevdir. assertThrowsYöntem, istisna beklenen türü atılır fonksiyonu ve doğrulama yürütür.

Assertions sınıfı ve yöntemleri

org.junit.jupiter.api.TestAçıklama bir test yöntemi belirtmektedir. @TestEk açıklamanın artık JUnit 4'ün paketi yerine JUnit 5 Jupiter API paketinden geldiğini unutmayın org.junit. Yöntem, testConvertToDecimalSuccessyöntemi önce MathTools::convertToDecimal3 pay ve 4 paydası ile çalıştırır , ardından sonucun 0,75'e eşit olduğunu ileri sürer. org.junit.jupiter.api.AssertionsSınıf kümesi sağlar staticfiili ve beklenen sonuçları karşılaştırmak için yöntemlerle. AssertionsSınıf temel veri türlerinin en kapak aşağıdaki yöntemleri, var

  • assertArrayEquals gerçek bir dizinin içeriğini beklenen bir diziyle karşılaştırır.
  • assertEquals gerçek bir değeri beklenen bir değerle karşılaştırır.
  • assertNotEquals eşit olmadıklarını doğrulamak için iki değeri karşılaştırır.
  • assertTrue sağlanan değerin doğru olduğunu doğrular.
  • assertFalse sağlanan değerin yanlış olduğunu doğrular.
  • assertLinesMatchiki Strings listesini karşılaştırır .
  • assertNull validates that the provided value is null.
  • assertNotNull validates that the provided value is not null.
  • assertSame validates that two values reference the same object.
  • assertNotSame validates that two values do not reference the same object.
  • assertThrows validates that the execution of a method throws an expected exception (you can see this in the testConvertToDecimalInvalidDenominator example above).
  • assertTimeout validates that a supplied function completes within a specified timeout.
  • assertTimeoutPreemptively validates that a supplied function completes within a specified timeout, but once the timeout is reached it kills the function's execution.

If any of these assertion methods fail, the unit test is marked as failed. That failure notice will be written to the screen when you run the test, then saved in a report file.

Using delta with assertEquals

When using float and double values in an assertEquals, you can also specify a delta that represents a threshold of difference between the two. In our example we could have added a delta of 0.001, in case 0.75 was actually returned as 0.750001.

Analyzing your test results

In addition to validating a value or behavior, the assert methods can also accept a textual description of the error, which can help you diagnose failures. For example:

 Assertions.assertEquals(0.75, result, "The MathTools::convertToDecimal value did not return the correct value of 0.75 for 3/4"); Assertions.assertEquals(0.75, result, () -> "The MathTools::convertToDecimal value did not return the correct value of 0.75 for 3/4"); 

The output will show the expected value of 0.75 and the actual value. It will also display the specified message, which can help you understand the context of the error. The difference between the two variations is that the first one always creates the message, even if it is not displayed, whereas the second one only constructs the message if the assertion fails. In this case, the construction of the message is trivial, so it doesn't really matter. Still, there is no need to construct an error message for a test that passes, so it's usually a best practice to use the second style.

Finally, if you're using an IDE like IntelliJ to run your tests, each test method will be displayed by its method name. This is fine if your method names are readable, but you can also add a @DisplayName annotation to your test methods to better identify the tests:

@Test @DisplayName("Test successful decimal conversion") void testConvertToDecimalSuccess() { double result = MathTools.convertToDecimal(3, 4); Assertions.assertEquals(0.751, result); }

Running your unit test

In order to run JUnit 5 tests from a Maven project, you need to include the maven-surefire-plugin in the Maven pom.xml file and add a new dependency. Listing 3 shows the pom.xml file for this project.

Listing 3. Maven pom.xml for an example JUnit 5 project

  4.0.0 com.javaworld.geekcap junit5 jar 1.0-SNAPSHOT    org.apache.maven.plugins maven-compiler-plugin 3.8.1  8 8    org.apache.maven.plugins maven-surefire-plugin 3.0.0-M4    junit5 //maven.apache.org   org.junit.jupiter junit-jupiter 5.6.0 test   

JUnit 5 dependencies

JUnit 5 packages its components in the org.junit.jupiter group and we need to add the junit-jupiter artifact, which is an aggregator artifact that imports the following dependencies:

  • junit-jupiter-api defines the API for writing tests and extensions.
  • junit-jupiter-engine birim testlerini çalıştıran test motoru uygulamasıdır.
  • junit-jupiter-params parametreli testler için destek sağlar.

Ardından, maven-surefire-plugintestleri çalıştırmak için build eklentisini eklememiz gerekiyor .

Son olarak, maven-compiler-pluginlambdas gibi Java 8 özelliklerini kullanabilmeniz için Java 8 veya sonraki bir sürümüne sahip olduğunuzdan emin olun .

Çalıştır şunu!

IDE'nizden veya Maven'den test sınıfını çalıştırmak için aşağıdaki komutu kullanın:

mvn clean test

Başarılıysanız, aşağıdakine benzer bir çıktı görmelisiniz:

 [INFO] ------------------------------------------------------- [INFO] T E S T S [INFO] ------------------------------------------------------- [INFO] Running com.javaworld.geekcap.math.MathToolsTest [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.04 s - in com.javaworld.geekcap.math.MathToolsTest [INFO] [INFO] Results: [INFO] [INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 [INFO] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 3.832 s [INFO] Finished at: 2020-02-16T08:21:15-05:00 [INFO] ------------------------------------------------------------------------