स्वचालित वेब अनुप्रयोग परीक्षण (MS Unit टेस्टिंग फ्रेमवर्क + सेलेनियम वेबड्राइवर C #)। भाग 2.2: सेलेनियम एपीआई आवरण - वेबइमेंट

सेलेनियम + सी #
परिचय

नमस्ते! पिछले भाग में, मैंने सेलेनियम वेबड्राइवर के साथ काम करते समय आने वाली मुख्य समस्याओं का वर्णन किया, और एक ब्राउज़र आवरण का उदाहरण भी दिया। यह मुश्किल नहीं लगता, ठीक है?) ठीक है, चलो आगे बढ़ते हैं। हमें शेष समस्याओं से निपटने की आवश्यकता है:

इस भाग में, हम रैपर वेबइलमेंट लिखेंगे, जो पूरी तरह से उपयोगकर्ता के लिए लक्षित है, अर्थात्। ऑटोटेस्ट्स के डेवलपर्स। मैं स्वीकार करता हूं कि लेखन के समय, मेरा कार्य एक "ढांचा" तैयार करना था जिसे मैनुअल परीक्षण इंजीनियरों को ऑटोटेस्ट लिखने के लिए उपयोग करना चाहिए। स्वाभाविक रूप से, उन्हें बहुत मामूली प्रोग्रामिंग ज्ञान होना चाहिए था। इसलिए, यह बिल्कुल महत्वपूर्ण नहीं था कि फ्रेम में स्वयं कितने टन कोड होंगे और यह अंदर कितना जटिल होगा। मुख्य बात यह है कि इसके बाहर तीन अक्षरों के समान सरल है। मैं आपको चेतावनी देता हूं, बहुत सारे कोड और कुछ चित्र होंगे =)

संदर्भ

भाग 1: परिचय
भाग 2.1: सेलेनियम एपीआई आवरण - ब्राउज़र
भाग 2.2: सेलेनियम एपीआई आवरण - वेबइमेंट
भाग 3: वेबपेज - पृष्ठों का वर्णन करना
भाग 4: अंत में परीक्षण लिखना
एक रूपरेखा प्रकाशित करना

आगे बढ़ो!

और इसलिए, मैंने यह सोचना शुरू कर दिया कि वेब तत्वों का वर्णन करने के लिए ऑटोटिस्ट्स के डेवलपर के रूप में यह मेरे लिए कैसे सुविधाजनक होगा। पहली समस्या कुछ डेवलपर्स गेटर्स लिखकर हल करते हैं, यह इस तरह दिखता है:
private IWebElement LoginEdit { get { return WebDriver.FindElement(By.Id("Login")); } } 

यदि कोई विशिष्ट गुण नहीं हैं, तो आपको फाइंडलाइज़ का उपयोग करके गुणों के सेट द्वारा खोजना होगा, और फिर गेटअट्रीब्यूट और गेटकवैल्यू का उपयोग करके उन्हें फ़िल्टर करना होगा।

WebDriver.Support में PageFactory और FindsBy विशेषता जैसी सुविधा है:
 [FindsBy(How = How.LinkText, Using = "")] public IWebElement HelpLink { get; set; } 

गुणों का वर्णन गुणों के माध्यम से किया जाता है - बुरा नहीं। इसके अलावा, खोज (CacheLookup) को कैश करना संभव है। इस निर्णय के विपक्ष:

सिद्धांत रूप में, कई इस पर रोकते हैं। हम और आगे जायेंगे। मैं कुछ मुनाफे की रूपरेखा तैयार करूंगा, जिन्हें मैं बाहर के रास्ते पर लाना चाहता हूं।

आइडिया और उपयोग के मामले

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

तत्वों को एक पंक्ति में वर्णन करना भी बहुत सुविधाजनक होगा, लेकिन गुणों के सरणियों को बनाने और पारित करने के लिए नहीं। और यहाँ, वैसे, हमारे पास "कॉल चेन" पैटर्न है। मापदंडों की घटना से तत्वों की खोज करने में सक्षम होना भी आवश्यक है।

खैर, पूरी खुशी के लिए, लिनक-शैली के तत्वों पर समूह विधियों को लागू करना आवश्यक है, उदाहरण के लिए, ताकि आप कुछ मानदंडों के अनुसार सभी चेकबॉक्स को नीचे रख सकें या लिंक की एक सरणी से स्ट्रिंग की एक सरणी प्राप्त कर सकें।

मैं WebElement योजना को चित्रित करने का प्रयास करूंगा:
छवि

मैं ध्यान देता हूं कि सभी एक ही, एक जटिल अनुप्रयोग का परीक्षण करते समय, आप ऐसी स्थितियों का सामना कर सकते हैं जब किसी तत्व को सेलेनियम वेबड्राइवर का उपयोग करके पहचाना नहीं जा सकता। इस समस्या को हल करने के लिए, Browser.ExecuteJavaScript विधि प्रदान की जाती है (पिछले लेख देखें), अर्थात। जावास्क्रिप्ट और jQuery के माध्यम से तत्वों के साथ काम करना संभव है।

आवरण कोड पर जाने से पहले, मैं विवरण के उदाहरण दिखाऊंगा:

आईडी द्वारा खोजें:
 private static readonly WebElement TestElement = new WebElement().ById("StartButton"); 

XPath खोज:
 private static readonly WebElement TestElement = new WebElement().ByXPath("//div[@class='Content']//tr[2]/td[2]"); 

वर्ग द्वारा अंतिम आइटम खोजें:
 private static readonly WebElement TestElement = new WebElement().ByClass("UserAvatar").Last(); 

विशेषता में मूल्य द्वारा खोजें:
 private static readonly WebElement TestElement = new WebElement().ByAttribute(TagAttributes.Href, "TagEdit", exactMatch: false); 

कई मापदंडों द्वारा खोजें:
 private static readonly WebElement TestElement = new WebElement().ByClass("TimePart").ByName("Day").Index(0); 

टैग और पाठ (प्रविष्टि) द्वारा खोजें:
 private static readonly WebElement TestElement = new WebElement().ByTagName(TagNames.Link).ByText("Hello", exactMach); 

ध्यान दें कि TestElement आवश्यक रूप से एक तत्व के लिए विवरण संग्रहीत नहीं करता है। यदि कई तत्व हैं, तो जब आप क्लिक करने का प्रयास करते हैं, तो एक अपवाद होना चाहिए (लेकिन मेरे कार्यान्वयन में पहला तत्व जो सामने आएगा उसका उपयोग किया जाएगा)। हम सूचकांक (...), या तो पहले () या अंतिम () का उपयोग करके किसी तत्व के सूचकांक को निर्दिष्ट करने में सक्षम हैं, ताकि एक तत्व को खोजने की गारंटी हो। इसके अलावा, एक तत्व के साथ एक क्रिया करने के लिए आवश्यक नहीं है, आप इसे एक बार में सभी तत्वों के साथ प्रदर्शन कर सकते हैं (नीचे दिए गए उदाहरणों में ForEach देखें)।

और अब मैं उपयोग का उदाहरण दूंगा:

आइटम पर क्लिक करें
 TestElement.Click(); 

सेलेनियम वेबड्राइवर या jQuery का उपयोग करके एक तत्व पर क्लिक करें:
 TestElement.Click(useJQuery: true); 

पाठ को पुनः प्राप्त करना (जैसे लिंक या पाठ क्षेत्र):
 var text = TestElement.Text; 

पाठ सेटिंग:
 TestElement.Text = "Hello!"; 

किसी आइटम को दूसरे आइटम पर खींचना:
 TestElement1.DragAndDrop(TestElement2); 

एक तत्व को एक घटना भेजना:
 TestElement.FireJQueryEvent(JavaScriptEvents.KeyUp); 

सभी ध्वस्त तत्वों (प्लसस पर क्लिक करें) का विस्तार करना:
 TestElements.ForEach(i => i.Click()); 

सभी हेडर का मूल्य प्राप्त करना:
 var subjects = new WebElement().ByClass("Subject").Select(i => i.Text); 


लागू कॉल श्रृंखला पैटर्न आपको एक साथ एक तत्व निर्धारित करने और एक क्रिया करने की अनुमति देता है:
 new WebElement().ById("Next").Click(); var text = new WebElement().ById("Help").Text; 

अंतिम उपयोगकर्ता के लिए (ऑटोटिस्ट्स का डेवलपर, जो पृष्ठों के तत्वों का वर्णन करेगा), यह बहुत ही अनुकूल लगता है, है ना? कुछ भी नहीं चिपका। कृपया ध्यान दें कि हम डेवलपर को मनमानी विशेषताओं को पारित करने की अनुमति नहीं देते हैं और इसके लिए enum TagAttributes और TagNames का उपयोग करते हुए मापदंडों को टैग करते हैं। यह कई जादू के तार से कोड को बचाएगा।

दुर्भाग्य से, ऐसे एपीआई प्रदान करने के लिए, आपको बहुत सारे कोड लिखना होगा। WebElement (आंशिक) वर्ग को 5 भागों में विभाजित किया जाएगा:

जैसा कि मैंने पहले ही पिछले लेख में चेतावनी दी है, कोड में कोई टिप्पणी नहीं है, लेकिन मैं कॉपी-पेस्ट के तहत मुख्य बिंदुओं पर टिप्पणी करने की कोशिश करूंगा।

WebElement.cs

 namespace Autotests.Utilities.WebElement { public partial class WebElement : ICloneable { private By _firstSelector; private IList<IWebElement> _searchCache; private IWebElement FindSingle() { return TryFindSingle(); } private IWebElement TryFindSingle() { Contract.Ensures(Contract.Result<IWebElement>() != null); try { return FindSingleIWebElement(); } catch (StaleElementReferenceException) { ClearSearchResultCache(); return FindSingleIWebElement(); } catch (InvalidSelectorException) { throw; } catch (WebDriverException) { throw; } catch (WebElementNotFoundException) { throw; } catch { throw WebElementNotFoundException; } } private IWebElement FindSingleIWebElement() { var elements = FindIWebElements(); if (!elements.Any()) throw WebElementNotFoundException; var element = elements.Count() == 1 ? elements.Single() : _index == -1 ? elements.Last() : elements.ElementAt(_index); // ReSharper disable UnusedVariable var elementAccess = element.Enabled; // ReSharper restore UnusedVariable return element; } private IList<IWebElement> FindIWebElements() { if (_searchCache != null) { return _searchCache; } Browser.WaitReadyState(); Browser.WaitAjax(); var resultEnumerable = Browser.FindElements(_firstSelector); try { resultEnumerable = FilterByVisibility(resultEnumerable).ToList(); resultEnumerable = FilterByTagNames(resultEnumerable).ToList(); resultEnumerable = FilterByText(resultEnumerable).ToList(); resultEnumerable = FilterByTagAttributes(resultEnumerable).ToList(); resultEnumerable = resultEnumerable.ToList(); } catch (Exception e) { Console.WriteLine(e); return new List<IWebElement>(); } var resultList = resultEnumerable.ToList(); return resultList; } private WebElementNotFoundException WebElementNotFoundException { get { CheckConnectionFailure(); return new WebElementNotFoundException(string.Format("Can't find single element with given search criteria: {0}.", SearchCriteriaToString())); } } private static void CheckConnectionFailure() { const string connectionFailure = "connectionFailure"; Contract.Assert(!Browser.PageSource.Contains(connectionFailure), "Connection can't be established."); } object ICloneable.Clone() { return Clone(); } public WebElement Clone() { return (WebElement)MemberwiseClone(); } } } 

यहां, मुख्य ध्यान FindIWebElements, FindSingleIWebElement और TryFindSingle में अपवाद हैंडलिंग पर ध्यान दिया जाता है। FindIWebElements में, हम अपने सभी काम (WaitReadyState और WaitAjax) को पूरा करने के लिए ब्राउज़र की प्रतीक्षा करते हैं, तत्वों की खोज करते हैं (FindElements), और फिर उन्हें विभिन्न मानदंडों के अनुसार फ़िल्टर करते हैं। इसके अलावा, _searchCache कोड में दिखाई देता है, यह सिर्फ हमारा कैश है (खोज स्वचालित रूप से कैश नहीं की गई है, आपको तत्व पर CacheSearchResult विधि को कॉल करने की आवश्यकता है)।

WebElementActions.cs

 namespace Autotests.Utilities.WebElement { internal enum SelectTypes { ByValue, ByText } public partial class WebElement { #region Common properties public int Count { get { return FindIWebElements().Count; } } public bool Enabled { get { return FindSingle().Enabled; } } public bool Displayed { get { return FindSingle().Displayed; } } public bool Selected { get { return FindSingle().Selected; } } public string Text { set { var element = FindSingle(); if (element.TagName == EnumHelper.GetEnumDescription(TagNames.Input) || element.TagName == EnumHelper.GetEnumDescription(TagNames.TextArea)) { element.Clear(); } else { element.SendKeys(Keys.LeftControl + "a"); element.SendKeys(Keys.Delete); } if (string.IsNullOrEmpty(value)) return; Browser.ExecuteJavaScript(string.Format("arguments[0].value = \"{0}\";", value), element); Executor.Try(() => FireJQueryEvent(JavaScriptEvents.KeyUp)); } get { var element = FindSingle(); return !string.IsNullOrEmpty(element.Text) ? element.Text : element.GetAttribute(EnumHelper.GetEnumDescription(TagAttributes.Value)); } } public int TextInt { set { Text = value.ToString(CultureInfo.InvariantCulture); } get { return Text.ToInt(); } } public string InnerHtml { get { return Browser.ExecuteJavaScript("return arguments[0].innerHTML;", FindSingle()).ToString(); } } #endregion #region Common methods public bool Exists() { return FindIWebElements().Any(); } public bool Exists(TimeSpan timeSpan) { return Executor.SpinWait(Exists, timeSpan, TimeSpan.FromMilliseconds(200)); } public bool Exists(int seconds) { return Executor.SpinWait(Exists, TimeSpan.FromSeconds(seconds), TimeSpan.FromMilliseconds(200)); } public void Click(bool useJQuery = true) { var element = FindSingle(); Contract.Assert(element.Enabled); if (useJQuery && element.TagName != EnumHelper.GetEnumDescription(TagNames.Link)) { FireJQueryEvent(element, JavaScriptEvents.Click); } else { try { element.Click(); } catch (InvalidOperationException e) { if (e.Message.Contains("Element is not clickable")) { Thread.Sleep(2000); element.Click(); } } } } public void SendKeys(string keys) { FindSingle().SendKeys(keys); } public void SetCheck(bool value, bool useJQuery = true) { var element = FindSingle(); Contract.Assert(element.Enabled); const int tryCount = 10; for (var i = 0; i < tryCount; i++) { element = FindSingle(); Set(value, useJQuery); if (element.Selected == value) { return; } } Contract.Assert(element.Selected == value); } public void Select(string optionValue) { SelectCommon(optionValue, SelectTypes.ByValue); } public void Select(int optionValue) { SelectCommon(optionValue.ToString(CultureInfo.InvariantCulture), SelectTypes.ByValue); } public void SelectByText(string optionText) { SelectCommon(optionText, SelectTypes.ByText); } public string GetAttribute(TagAttributes tagAttribute) { return FindSingle().GetAttribute(EnumHelper.GetEnumDescription(tagAttribute)); } #endregion #region Additional methods public void SwitchContext() { var element = FindSingle(); Browser.SwitchToFrame(element); } public void CacheSearchResult() { _searchCache = FindIWebElements(); } public void ClearSearchResultCache() { _searchCache = null; } public void DragAndDrop(WebElement destination) { var source = FindSingle(); var dest = destination.FindSingle(); Browser.DragAndDrop(source, dest); } public void FireJQueryEvent(JavaScriptEvents javaScriptEvent) { var element = FindSingle(); FireJQueryEvent(element, javaScriptEvent); } public void ForEach(Action<WebElement> action) { Contract.Requires(action != null); CacheSearchResult(); Enumerable.Range(0, Count).ToList().ForEach(i => action(ByIndex(i))); ClearSearchResultCache(); } public List<T> Select<T>(Func<WebElement, T> action) { Contract.Requires(action != null); var result = new List<T>(); ForEach(e => result.Add(action(e))); return result; } public List<WebElement> Where(Func<WebElement, bool> action) { Contract.Requires(action != null); var result = new List<WebElement>(); ForEach(e => { if (action(e)) result.Add(e); }); return result; } public WebElement Single(Func<WebElement, bool> action) { return Where(action).Single(); } #endregion #region Helpers private void Set(bool value, bool useJQuery = true) { if (Selected ^ value) { Click(useJQuery); } } private void SelectCommon(string option, SelectTypes selectType) { Contract.Requires(!string.IsNullOrEmpty(option)); var element = FindSingle(); Contract.Assert(element.Enabled); switch (selectType) { case SelectTypes.ByValue: new SelectElement(element).SelectByValue(option); return; case SelectTypes.ByText: new SelectElement(element).SelectByText(option); return; default: throw new Exception(string.Format("Unknown select type: {0}.", selectType)); } } private void FireJQueryEvent(IWebElement element, JavaScriptEvents javaScriptEvent) { var eventName = EnumHelper.GetEnumDescription(javaScriptEvent); Browser.ExecuteJavaScript(string.Format("$(arguments[0]).{0}();", eventName), element); } #endregion } public enum JavaScriptEvents { [Description("keyup")] KeyUp, [Description("click")] Click } } 

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

WebElementByCriteria.cs

 namespace Autotests.Utilities.WebElement { internal class SearchProperty { public string AttributeName { get; set; } public string AttributeValue { get; set; } public bool ExactMatch { get; set; } } internal class TextSearchData { public string Text { get; set; } public bool ExactMatch { get; set; } } public partial class WebElement { private readonly IList<SearchProperty> _searchProperties = new List<SearchProperty>(); private readonly IList<TagNames> _searchTags = new List<TagNames>(); private bool _searchHidden; private int _index; private string _xPath; private TextSearchData _textSearchData; public WebElement ByAttribute(TagAttributes tagAttribute, string attributeValue, bool exactMatch = true) { return ByAttribute(EnumHelper.GetEnumDescription(tagAttribute), attributeValue, exactMatch); } public WebElement ByAttribute(TagAttributes tagAttribute, int attributeValue, bool exactMatch = true) { return ByAttribute(EnumHelper.GetEnumDescription(tagAttribute), attributeValue.ToString(), exactMatch); } public WebElement ById(string id, bool exactMatch = true) { return ByAttribute(TagAttributes.Id, id, exactMatch); } public WebElement ById(int id, bool exactMatch = true) { return ByAttribute(TagAttributes.Id, id.ToString(), exactMatch); } public WebElement ByName(string name, bool exactMatch = true) { return ByAttribute(TagAttributes.Name, name, exactMatch); } public WebElement ByClass(string className, bool exactMatch = true) { return ByAttribute(TagAttributes.Class, className, exactMatch); } public WebElement ByTagName(TagNames tagName) { var selector = By.TagName(EnumHelper.GetEnumDescription(tagName)); _firstSelector = _firstSelector ?? selector; _searchTags.Add(tagName); return this; } public WebElement ByXPath(string xPath) { Contract.Assume(_firstSelector == null, "XPath can be only the first search criteria."); _firstSelector = By.XPath(xPath); _xPath = xPath; return this; } public WebElement ByIndex(int index) { _index = index; return this; } public WebElement First() { _index = 0; return this; } public WebElement Last() { _index = -1; return this; } public WebElement IncludeHidden() { _searchHidden = true; return this; } public WebElement ByText(string text, bool exactMatch = true) { var selector = exactMatch ? By.XPath(string.Format("//*[text()=\"{0}\"]", text)) : By.XPath(string.Format("//*[contains(text(), \"{0}\")]", text)); _firstSelector = _firstSelector ?? selector; _textSearchData = new TextSearchData { Text = text, ExactMatch = exactMatch }; return this; } private WebElement ByAttribute(string tagAttribute, string attributeValue, bool exactMatch = true) { var xPath = exactMatch ? string.Format("//*[@{0}=\"{1}\"]", tagAttribute, attributeValue) : string.Format("//*[contains(@{0}, \"{1}\")]", tagAttribute, attributeValue); var selector = By.XPath(xPath); _firstSelector = _firstSelector ?? selector; _searchProperties.Add(new SearchProperty { AttributeName = tagAttribute, AttributeValue = attributeValue, ExactMatch = exactMatch }); return this; } private string SearchCriteriaToString() { var result = _searchProperties.Select(searchProperty => string.Format("{0}: {1} ({2})", searchProperty.AttributeName, searchProperty.AttributeValue, searchProperty.ExactMatch ? "exact" : "contains")).ToList(); result.AddRange(_searchTags.Select(searchTag => string.Format("tag: {0}", searchTag))); if (_xPath != null) { result.Add(string.Format("XPath: {0}", _xPath)); } if (_textSearchData != null) { result.Add(string.Format("text: {0} ({1})", _textSearchData.Text, _textSearchData.ExactMatch ? "exact" : "contains")); } return string.Join(", ", result); } } } 

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

WebElementExceptions.cs

 namespace Autotests.Utilities.WebElement { public class WebElementNotFoundException : Exception { public WebElementNotFoundException(string message) : base(message) { } } } 

खैर, केवल एक कस्टम अपवाद है।

WebElementFilters.cs

 namespace Autotests.Utilities.WebElement { public partial class WebElement { private IEnumerable<IWebElement> FilterByVisibility(IEnumerable<IWebElement> result) { return !_searchHidden ? result.Where(item => item.Displayed) : result; } private IEnumerable<IWebElement> FilterByTagNames(IEnumerable<IWebElement> elements) { return _searchTags.Aggregate(elements, (current, tag) => current.Where(item => item.TagName == EnumHelper.GetEnumDescription(tag))); } private IEnumerable<IWebElement> FilterByText(IEnumerable<IWebElement> result) { if (_textSearchData != null) { result = _textSearchData.ExactMatch ? result.Where(item => item.Text == _textSearchData.Text) : result.Where(item => item.Text.Contains(_textSearchData.Text, StringComparison.InvariantCultureIgnoreCase)); } return result; } private IEnumerable<IWebElement> FilterByTagAttributes(IEnumerable<IWebElement> elements) { return _searchProperties.Aggregate(elements, FilterByTagAttribute); } private static IEnumerable<IWebElement> FilterByTagAttribute(IEnumerable<IWebElement> elements, SearchProperty searchProperty) { return searchProperty.ExactMatch ? elements.Where(item => item.GetAttribute(searchProperty.AttributeName) != null && item.GetAttribute(searchProperty.AttributeName).Equals(searchProperty.AttributeValue)) : elements.Where(item => item.GetAttribute(searchProperty.AttributeName) != null && item.GetAttribute(searchProperty.AttributeName).Contains(searchProperty.AttributeValue)); } } } 

फ़िल्टर को आइटम खोजने के लिए FindIWebElements (WebElement.cs फ़ाइल) में कहा जाता है। मैं केवल इस बात पर ध्यान देता हूं कि बड़े डेटासेट्स के साथ, लाइनक फॉरएच और फॉरचेक की तुलना में अधिक लंबे समय तक काम करता है, इसलिए यह क्लासिक लूप्स का उपयोग करके इस कोड को फिर से लिखना समझ सकता है।

निष्कर्ष

मैं एक बार फिर लेख में किए गए लेखों में त्रुटियों, साथ ही टिप्पणियों में कोई प्रश्न देखूंगा।

टिप्पणी

- लेख Enum, EnumHelper और Executor के लिए कोड प्रदान नहीं करता है। मैं अंतिम भाग में पूर्ण कोड पोस्ट करूंगा
- string.Contains मेथड का प्रयोग एक एक्सटेंशन है:
 public static bool Contains(this string source, string target, StringComparison stringComparison) { return source.IndexOf(target, stringComparison) >= 0; } 

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


All Articles