जावा में परीक्षण। TestNG


निश्चित रूप से हर कोई परीक्षण-संचालित विकास (टीडीडी) की अवधारणा से परिचित है। इसके साथ, डेटा-संचालित परीक्षण (डीडीटी, शेवचुक के लिए कोई अपराध नहीं) जैसी कोई चीज भी है - परीक्षण लिखने के लिए एक तकनीक जिसमें परीक्षणों के लिए डेटा स्वयं परीक्षणों से अलग संग्रहीत होता है। उन्हें परीक्षण निष्पादन के दौरान उत्पन्न डेटाबेस, फ़ाइल में संग्रहीत किया जा सकता है। यह बहुत सुविधाजनक है, क्योंकि एक ही कार्यक्षमता को विभिन्न डेटा सेटों पर परीक्षण किया जाता है, जबकि इस डेटा को जोड़ना, हटाना या बदलना अधिकतम सरलीकृत है।

पिछले लेख में, मैंने JUnit की क्षमताओं की जांच की। पैरामीटर और थ्योरी लॉन्चर इस दृष्टिकोण के उदाहरण के रूप में काम कर सकते हैं, दोनों ही मामलों में एक टेस्ट क्लास में केवल एक ही ऐसा पैरामीटराइज्ड टेस्ट ( Parameterized कई के मामले में हो सकता है, लेकिन वे सभी एक ही डेटा का उपयोग करेंगे)।

इस लेख में, मैं TestNG परीक्षण ढांचे पर ध्यान केंद्रित करूंगा। कई लोग इस नाम को पहले ही सुन चुके हैं, और इसे बंद करने के बाद, वे शायद ही जेयूनिट में वापस जाना चाहते हैं (हालाँकि यह केवल एक धारणा है)।

मुख्य विशेषताएं


तो वहाँ क्या है? जेयूनीट 4 की तरह, परीक्षणों में एनोटेशन का उपयोग किया जाता है, और जेयूनिट 3 में लिखे गए परीक्षणों का भी समर्थन किया जाता है। एनोटेशन के बजाय डॉकलेट का उपयोग करना संभव है।

शुरू करने के लिए, परीक्षणों के पदानुक्रम पर विचार करें। सभी परीक्षण किसी भी परीक्षण अनुक्रम (सुइट) से संबंधित हैं, इसमें कई कक्षाएं शामिल हैं, जिनमें से प्रत्येक में कई परीक्षण विधियां शामिल हो सकती हैं। इसके अलावा, कक्षाएं और परीक्षण विधियां एक निश्चित समूह से संबंधित हो सकती हैं। यह नेत्रहीन इस तरह दिखता है:
+- suite/ +- test0/ | +- class0/ | | +- method0(integration group)/ | | +- method1(functional group)/ | | +- method2/ | +- class1 | +- method3(optional group)/ +- test1/ +- class3(optional group, integration group)/ +- method4/ 

इस पदानुक्रम का प्रत्येक सदस्य विन्यासकर्ताओं के पहले और बाद में हो सकता है। यह इस क्रम में सभी शुरू होता है:
 +- before suite/ +- before group/ +- before test/ +- before class/ +- before method/ +- test/ +- after method/ ... +- after class/ ... +- after test/ ... +- after group/ ... +- after suite/ 

अब परीक्षणों के बारे में और अधिक। एक उदाहरण पर विचार करें। उन स्थानों के साथ काम करने की उपयोगिता जो स्ट्रिंग से पार्स कर सकते हैं, साथ ही उम्मीदवारों की खोज कर सकते हैं ( en_US -> en_US, en, root ):
 public abstract class LocaleUtils { /** * Root locale fix for java 1.5 */ public static final Locale ROOT_LOCALE = new Locale(""); private static final String LOCALE_SEPARATOR = "_"; public static Locale parseLocale(final String value) { if (value != null) { final StringTokenizer tokens = new StringTokenizer(value, LOCALE_SEPARATOR); final String language = tokens.hasMoreTokens() ? tokens.nextToken() : ""; final String country = tokens.hasMoreTokens() ? tokens.nextToken() : ""; String variant = ""; String sep = ""; while (tokens.hasMoreTokens()) { variant += sep + tokens.nextToken(); sep = LOCALE_SEPARATOR; } return new Locale(language, country, variant); } return null; } public static List<Locale> getCandidateLocales(final Locale locale) { final List<Locale> locales = new ArrayList<Locale>(); if (locale != null) { final String language = locale.getLanguage(); final String country = locale.getCountry(); final String variant = locale.getVariant(); if (variant.length() > 0) { locales.add(locale); } if (country.length() > 0) { locales.add((locales.size() == 0) ? locale : new Locale(language, country)); } if (language.length() > 0) { locales.add((locales.size() == 0) ? locale : new Locale(language)); } } locales.add(ROOT_LOCALE); return locales; } } 

हम JUnit-a की शैली में एक परीक्षण लिखेंगे (हमें इस उदाहरण को TestN पर परीक्षण लिखने के लिए एक मार्गदर्शक के रूप में नहीं समझना चाहिए):
 public class LocaleUtilsOldStyleTest extends Assert { private final Map<String, Locale> parseLocaleData = new HashMap<String, Locale>(); @BeforeClass private void setUp() { parseLocaleData.put(null, null); parseLocaleData.put("", LocaleUtils.ROOT_LOCALE); parseLocaleData.put("en", Locale.ENGLISH); parseLocaleData.put("en_US", Locale.US); parseLocaleData.put("en_GB", Locale.UK); parseLocaleData.put("ru", new Locale("ru")); parseLocaleData.put("ru_RU_xxx", new Locale("ru", "RU", "xxx")); } @AfterTest void tearDown() { parseLocaleData.clear(); } @Test public void testParseLocale() { for (Map.Entry<String, Locale> entry : parseLocaleData.entrySet()) { final Locale actual = LocaleUtils.parseLocale(entry.getKey()); final Locale expected = entry.getValue(); assertEquals(actual, expected); } } } 

वहाँ क्या है?

इन सभी एनोटेशन में निम्नलिखित विकल्प हैं:जैसा कि आप उदाहरण से देख सकते हैं, परीक्षण व्यावहारिक रूप से JUnit पर एक ही परीक्षण से अलग नहीं है। अगर कोई अंतर नहीं है, तो टेस्टएनजी का उपयोग क्यों करें?

परिमार्जित परीक्षण


आइए एक ही परीक्षा को दूसरे तरीके से लिखें:
 public class LocaleUtilsTest extends Assert { @DataProvider public Object[][] parseLocaleData() { return new Object[][]{ {null, null}, {"", LocaleUtils.ROOT_LOCALE}, {"en", Locale.ENGLISH}, {"en_US", Locale.US}, {"en_GB", Locale.UK}, {"ru", new Locale("ru")}, {"ru_RU_some_variant", new Locale("ru", "RU", "some_variant")}, }; } @Test(dataProvider = "parseLocaleData") public void testParseLocale(String locale, Locale expected) { final Locale actual = LocaleUtils.parseLocale(locale); assertEquals(actual, expected); } } 

आसान? बेशक, डेटा को परीक्षण से ही अलग से संग्रहीत किया जाता है। क्या यह सुविधाजनक है? बेशक, आप parseLocaleData विधि में सिर्फ एक पंक्ति जोड़कर परीक्षण जोड़ सकते हैं।

तो यह कैसे काम करता है?
एक और उदाहरण, अब हम परीक्षण के डेटा और तर्क को विभिन्न वर्गों में फैलाएंगे:
 public class LocaleUtilsTestData { @DataProvider(name = "getCandidateLocalesData") public static Object[][] getCandidateLocalesData() { return new Object[][]{ {null, Arrays.asList(LocaleUtils.ROOT_LOCALE)}, {LocaleUtils.ROOT_LOCALE, Arrays.asList(LocaleUtils.ROOT_LOCALE)}, {Locale.ENGLISH, Arrays.asList(Locale.ENGLISH, LocaleUtils.ROOT_LOCALE)}, {Locale.US, Arrays.asList(Locale.US, Locale.ENGLISH, LocaleUtils.ROOT_LOCALE)}, {new Locale("en", "US", "xxx"), Arrays.asList( new Locale("en", "US", "xxx"), Locale.US, Locale.ENGLISH, LocaleUtils.ROOT_LOCALE) }, }; } } public class LocaleUtilsTest extends Assert { // other tests @Test(dataProvider = "getCandidateLocalesData", dataProviderClass = LocaleUtilsTestData.class) public void testGetCandidateLocales(Locale locale, List<Locale> expected) { final List<Locale> actual = LocaleUtils.getCandidateLocales(locale); assertEquals(actual, expected); } } 

इस स्थिति में, पैरामीटर डेटाप्रोवाइडरक्लास और डेटाप्रोवाइडर सेट होते हैं । परीक्षण डेटा लौटने की विधि स्थिर होनी चाहिए।

उपरोक्त के अलावा, परीक्षणों को मानकीकृत करने का एक और तरीका है। वांछित विधि @ पैरामीटर का उपयोग करके एनोटेट की जाती है , जहां सभी आवश्यक मापदंडों के नाम इंगित किए जाते हैं। कुछ मापदंडों को डिफ़ॉल्ट मान के साथ @ वैकल्पिक का उपयोग करके एनोटेट किया जा सकता है (यदि निर्दिष्ट नहीं है, तो प्राइमेट के लिए डिफ़ॉल्ट मान का उपयोग किया जाएगा, या अन्य सभी प्रकारों के लिए शून्य)। पैरामीटर मान TestNG कॉन्फ़िगरेशन (जिसे बाद में चर्चा की जाएगी) में संग्रहीत किया जाता है। एक उदाहरण:
 public class ParameterizedTest extends Assert { private DataSource dataSource; @Parameters({"driver", "url", "username", "password"}) @BeforeClass public void setUpDataSource(String driver, String url, @Optional("sa") String username, @Optional String password) { // create datasource dataSource = ... } @Test public void testOptionalData() throws SQLException { dataSource.getConnection(); // do some staff } } 

इस मामले में, setUpDataSource विधि डेटाबेस कनेक्शन सेटिंग्स को मापदंडों के रूप में स्वीकार करेगी, और उपयोगकर्ता नाम और पासवर्ड पैरामीटर वैकल्पिक हैं, दिए गए डिफ़ॉल्ट मानों के साथ। सभी परीक्षणों (अच्छी तरह से, या लगभग सभी) के लिए डेटा के साथ उपयोग करना बहुत सुविधाजनक है, उदाहरण के लिए, डेटाबेस कनेक्शन स्थापित करने के उदाहरण में।

खैर, निष्कर्ष में, कारखानों के बारे में कुछ शब्द कहा जाना चाहिए जो आपको गतिशील रूप से परीक्षण बनाने की अनुमति देते हैं। स्वयं परीक्षणों के साथ-साथ, उन्हें @DataProvider या @Parameters का उपयोग करके परिमाणित किया जा सकता है:
 public class FactoryTest { @DataProvider public Object[][] tablesData() { return new Object[][] { {"FIRST_TABLE"}, {"SECOND_TABLE"}, {"THIRD_TABLE"}, }; } @Factory(dataProvider = "tablesData") public Object[] createTest(String table) { return new Object[] { new GenericTableTest(table) }; } } public class GenericTableTest extends Assert { private final String table; public GenericTableTest(final String table) { this.table = table; } @Test public void testTable() { System.out.println(table); // do some testing staff here } } 

@ पैरामीटर्स के साथ विकल्प:
 public class FactoryTest { @Parameters("table") @Factory public Object[] createParameterizedTest(@Optional("SOME_TABLE") String table) { return new Object[] { new GenericTableTest(table) }; } } 

बहु सूत्रण


यह जांचने की आवश्यकता है कि एप्लिकेशन बहु-थ्रेडेड वातावरण में कैसे व्यवहार करता है? आप कई थ्रेड्स से एक साथ परीक्षण चला सकते हैं:
 public class ConcurrencyTest extends Assert { private Map<String, String> data; @BeforeClass void setUp() throws Exception { data = new HashMap<String, String>(); } @AfterClass void tearDown() throws Exception { data = null; } @Test(threadPoolSize = 30, invocationCount = 100, invocationTimeOut = 10000) public void testMapOperations() throws Exception { data.put("1", "111"); data.put("2", "111"); data.put("3", "111"); data.put("4", "111"); data.put("5", "111"); data.put("6", "111"); data.put("7", "111"); for (Map.Entry<String, String> entry : data.entrySet()) { System.out.println(entry); } data.clear(); } @Test(singleThreaded = true, invocationCount = 100, invocationTimeOut = 10000) public void testMapOperationsSafe() throws Exception { data.put("1", "111"); data.put("2", "111"); data.put("3", "111"); data.put("4", "111"); data.put("5", "111"); data.put("6", "111"); data.put("7", "111"); for (Map.Entry<String, String> entry : data.entrySet()) { System.out.println(entry); } data.clear(); } } 

पहला परीक्षण समय-समय पर समवर्ती विफल हो जाएगा, क्योंकि यह विभिन्न थ्रेड्स से चलाया जाएगा, दूसरा नहीं होगा, क्योंकि सभी परीक्षण एक धागे से क्रमिक रूप से चलाए जाएंगे।

आप प्रदाता तिथि के समानांतर पैरामीटर को भी सही पर सेट कर सकते हैं, फिर प्रत्येक डेटा सेट के लिए परीक्षण समानांतर में लॉन्च किए जाएंगे, एक अलग संख्या में:
 public class ConcurrencyTest extends Assert { // some staff here @DataProvider(parallel = true) public Object[][] concurrencyData() { return new Object[][] { {"1", "2"}, {"3", "4"}, {"5", "6"}, {"7", "8"}, {"9", "10"}, {"11", "12"}, {"13", "14"}, {"15", "16"}, {"17", "18"}, {"19", "20"}, }; } @Test(dataProvider = "concurrencyData") public void testParallelData(String first, String second) { final Thread thread = Thread.currentThread(); System.out.printf("#%d %s: %s : %s", thread.getId(), thread.getName(), first, second); System.out.println(); } } 

यह परीक्षण कुछ इस तरह उत्पादन करेगा:
 #16 pool-1-thread-3: 5 : 6 #19 pool-1-thread-6: 11 : 12 #14 pool-1-thread-1: 1 : 2 #22 pool-1-thread-9: 17 : 18 #20 pool-1-thread-7: 13 : 14 #18 pool-1-thread-5: 9 : 10 #15 pool-1-thread-2: 3 : 4 #17 pool-1-thread-4: 7 : 8 #21 pool-1-thread-8: 15 : 16 #23 pool-1-thread-10: 19 : 20 

इस पैरामीटर के बिना, कुछ ऐसा होगा:
 #1 main: 1 : 2 #1 main: 3 : 4 #1 main: 5 : 6 #1 main: 7 : 8 #1 main: 9 : 10 #1 main: 11 : 12 #1 main: 13 : 14 #1 main: 15 : 16 #1 main: 17 : 18 #1 main: 19 : 20 

अतिरिक्त विशेषताएं


वर्णित सब कुछ के अलावा, अन्य संभावनाएं हैं, उदाहरण के लिए, अपवादों को फेंकने की जांच करने के लिए (यह गलत डेटा पर परीक्षणों के लिए उपयोग करने के लिए बहुत सुविधाजनक है):
 public class ExceptionTest { @DataProvider public Object[][] wrongData() { return new Object[][] { {"Hello, World!!!"}, {"0x245"}, {"1798237199878129387197238"}, }; } @Test(dataProvider = "wrongData", expectedExceptions = NumberFormatException.class, expectedExceptionsMessageRegExp = "^For input string: \"(.*)\"$") public void testParse(String data) { Integer.parseInt(data); } } 

एक और उदाहरण:
 public class PrioritiesTest extends Assert { private boolean firstTestExecuted; @BeforeClass public void setUp() throws Exception { firstTestExecuted = false; } @Test(priority = 0) public void first() { assertFalse(firstTestExecuted); firstTestExecuted = true; } @Test(priority = 1) public void second() { assertTrue(firstTestExecuted); } } 

यह उदाहरण सफल होगा क्योंकि पहला तरीका पहले निष्पादित करेगा, फिर दूसरा । यदि आप पहली से 2 की प्राथमिकता बदलते हैं, तो परीक्षण विफल हो जाएगा।

एक समान व्यवहार भी देखा जाएगा यदि आप परीक्षण की निर्भरता निर्दिष्ट करते हैं, उदाहरण के लिए, हमारे परीक्षण में जोड़ें:
 public class PrioritiesTest extends Assert { // some staff @Test(dependsOnMethods = {"first"}) public void third() { assertTrue(firstTestExecuted); } // some staff } 

यह आमतौर पर सुविधाजनक होता है जब एक परीक्षण दूसरे पर निर्भर करता है, उदाहरण के लिए, यूटिलिटी 1 यूटिलिटी 0 का उपयोग करता है, अगर यूटिलिटी 0 सही ढंग से काम नहीं करता है, तो यह यूटिलिटी 1 का परीक्षण करने के लिए कोई मतलब नहीं है। दूसरी ओर, यह @Before , @ तरीकों में निर्भरता का उपयोग करने के लिए भी सुविधाजनक है, विशेष रूप से मूल परीक्षण को विरासत में मिली के साथ जोड़ने के लिए, और कभी-कभी यह सुनिश्चित करना आवश्यक है कि भले ही विधि विफल हो और विधि बी इस पर निर्भर करती है, विधि बी को अभी भी कहा जाता है। इस स्थिति में, हमेशा पैरामीटर पैरामीटर को सही पर सेट करें।

निर्भरता इंजेक्शन


मैं "अच्छे निगम" गुइसे से ढांचे के प्रशंसकों को खुश करना चाहता हूं। TestNG ने बाद के लिए अंतर्निहित समर्थन किया है। यह इस तरह दिखता है:
 public class GuiceModule extends AbstractModule { @Override protected void configure() { bind(String.class).annotatedWith(Names.named("guice-string-0")).toInstance("Hello, "); } @Named("guice-string-1") @Inject @Singleton @Provides public String provideGuiceString() { return "World!!!"; } } @Guice(modules = {GuiceModule.class}) public class GuiceTest extends Assert { @Inject @Named("guice-string-0") private String word0; @Inject @Named("guice-string-1") private String word1; @Test public void testService() { final String actual = word0 + word1; assertEquals(actual, "Hello, World!!!"); } } 

सभी आवश्यक है कि @Guice का उपयोग करके आवश्यक वर्ग को एनोटेट करें और मॉड्यूल पैरामीटर में सभी आवश्यक गार्ड मॉड्यूल निर्दिष्ट करें। परीक्षण वर्ग में आगे, आप पहले से ही @ इंजेक्शन का उपयोग करके निर्भरता इंजेक्शन का उपयोग कर सकते हैं

मैं यह भी जोड़ता हूं कि अन्य समान रूपरेखा के प्रशंसकों को परेशान नहीं होना चाहिए, क्योंकि आमतौर पर टेस्टएनजी के लिए उनका अपना समर्थन होता है, उदाहरण के लिए, वसंत।

कार्यक्षमता विस्तार


श्रोता तंत्र का उपयोग करके कार्यात्मक विस्तार को लागू किया जा सकता है। निम्नलिखित श्रोता प्रकार समर्थित हैं:
आप यहां श्रोता तंत्र के एक दिलचस्प उपयोग के एक उदाहरण के बारे में पढ़ सकते हैं।

विन्यास


अब हम परीक्षण कॉन्फ़िगरेशन पर चलते हैं। परीक्षण चलाने का सबसे सरल तरीका कुछ इस प्रकार है:
  final TestNG testNG = new TestNG(true); testNG.setTestClasses(new Class[] {SuperMegaTest.class}); testNG.setExcludedGroups("optional"); testNG.run(); 

लेकिन सबसे अधिक बार, XML या YAML कॉन्फ़िगरेशन का उपयोग परीक्षण चलाने के लिए किया जाता है। XML कॉन्फ़िगरेशन कुछ इस तरह दिखता है:
 <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd"> <suite name="Test suite" parallel="classes" thread-count="10"> <test name="Default tests" verbose="1" annotations="JDK" thread-count="10" parallel="classes"> <parameter name="driver" value="com.mysql.jdbc.Driver"/> <parameter name="url" value="jdbc:mysql://localhost:3306/db"/> <groups> <run> <exclude name="integration"/> </run> </groups> <packages> <package name="com.example.*"/> </packages> </test> <test name="Integration tests" annotations="JDK"> <groups> <run> <include name="integration"/> </run> </groups> <packages> <package name="com.example.*"/> </packages> </test> </suite> 

इसी तरह के YAML कॉन्फ़िगरेशन:
 name: YAML Test suite parallel: classes threadCount: 10 tests: - name: Default tests parameters: driver: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/db excludedGroups: [ integration ] packages: - com.example.* - name: Integration tests parameters: driver: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/db includedGroups: [ integration ] packages: - com.example.* 

फिर परीक्षण चलाने के लिए आपको निम्नलिखित कार्य करने होंगे:
  final TestNG testNG = new TestNG(true); //final Parser parser = new Parser("testing/testing-testng/src/test/resources/testng.xml"); final Parser parser = new Parser("testing/testing-testng/src/test/resources/testng.yaml"); final List<XmlSuite> suites = parser.parseToList(); testNG.setXmlSuites(suites); testNG.run(); 

हालांकि, शायद, प्रोग्रामेटिक रूप से चल रहे परीक्षण अनावश्यक हैं, क्योंकि आप इसके लिए आईडीई की क्षमताओं का उपयोग कर सकते हैं।

आइए कॉन्फ़िगरेशन पर वापस जाएं। उच्चतम स्तर पर, एक परीक्षण अनुक्रम (सुइट) स्थापित किया गया है। यह निम्नलिखित पैरामीटर ले सकता है:
भी कॉन्फ़िगर किया जा सकता है:
सूट, बदले में, सूट ( नाम, धागा-गणना, समानांतर, टाइम-आउट, जूनिट, एनोटेशन , पैरामीटर, पैकेज, विधि-चयनकर्ता टैग) के लिए लगभग समान सेटिंग्स के साथ परीक्षण शामिल कर सकते हैं। टेस्ट में अजीबोगरीब सेटिंग्स भी हैं, उदाहरण के लिए, समूह चलाएं:
  <test name="Default tests" verbose="1" annotations="JDK" thread-count="10" parallel="classes"> <!-- some staff here --> <groups> <run> <exclude name="integration"/> </run> </groups> <!-- some staff here --> </test> 

इस उदाहरण में, परीक्षण में केवल परीक्षण शामिल होंगे जो एकीकरण समूह का हिस्सा नहीं हैं।

अभी भी परीक्षणों में परीक्षण कक्षाएं शामिल हो सकती हैं, जो बदले में परीक्षण विधियों को शामिल / बहिष्कृत कर सकती हैं।
  <test name="Integration tests"> <groups> <run> <include name="integration"/> </run> </groups> <classes> <class name="com.example.PrioritiesTest"> <methods> <exclude name="third"/> </methods> </class> </classes> </test> 


निष्कर्ष


यह वह सब नहीं है जो इस अद्भुत ढांचे के लिए कहा जा सकता है, एक लेख में सब कुछ कवर करना बहुत मुश्किल है। लेकिन मैंने इसके उपयोग के मुख्य बिंदुओं को प्रकट करने का प्रयास किया। यहाँ, निश्चित रूप से, श्रोता तंत्र के बारे में कुछ और बता सकता है, अन्य रूपरेखाओं के साथ एकीकरण, परीक्षणों के विन्यास के बारे में, लेकिन फिर लेख एक किताब में बदल जाएगा, और मुझे सब कुछ पता नहीं है। लेकिन मुझे उम्मीद है कि यह लेख किसी के लिए उपयोगी था और जो कुछ कहा गया है वह कुछ पाठकों को टेस्टएनजी का उपयोग करने के लिए प्रोत्साहित करेगा, या कम से कम डीडीटी तकनीक को परीक्षण लिखने के लिए प्रोत्साहित करेगा।

उदाहरण यहां , विभिन्न लेख यहां देखे जा सकते हैं

साहित्य


Source: https://habr.com/ru/post/In121234/


All Articles