Всем привет. Подошло время рассмотреть шаблон Singleton (Одиночка). Он гарантирует, что в приложении существует не более одного экземпляра определенного класса. Также паттерн предоставляет глобальную точку доступа к этому экземпляру. К тому же, он использует приватный конструктор и статическое свойство в сочетании со статической переменной.
Составляющие классы:
- Page Objects (BingMainPage) – содержит действия, такие как Search и Navigate. Предоставляет доступ к классу валидации BingMainPageValidator. Сами объекты находятся в классе BingMainPageElementMap.
- BasePageSingleton<S, M> – инкапсулирует доступ к карте элементов (map), создавая свойство для доступа к объекту, а также определяет метод Navigate. См. предыдущую статью.
- BasePageSingleton<S, M, V> – добавляет создание объекта класса валидации.
- ThreadSafeNestedContructorsBaseSingleton<T> – класс непосредственной реализации синглтона.
Все остальные элементы остались без изменения (также можете обратиться к предыдущей статье).
Самый простой способ интегрировать Синглтон в наши тесты – добавить статическую переменную и свойство в базовый класс BasePage.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
public class BasePage<M> where M : BasePageElementMap, new() { private static BasePage<M> instance; protected readonly string url; public BasePage(string url) { this.url = url; } public BasePage() { this.url = null; } public static BasePage<M> Instance { get { if (instance == null) { instance = new BasePage<M>(); } return instance; } } protected M Map { get { return new M(); } } public virtual void Navigate(string part = ““) { Driver.Browser.Navigate().GoToUrl(string.Concat(url, part)); } } |
Недостатком такого варианта есть то, что он предотвращает создание классов в паттерне Facade.
Чтобы избежать этого, можно использовать не безопасный с точки зрения потоков класс BaseSingleton:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
namespace Singleton.Base { public abstract class NonThreadSafeBaseSingleton<T> where T: new() { private static T instance; public static T Instance { get { if (instance == null) { instance = new T(); } return instance; } } } } |
Для большинства случаев этот вариант подойдет. Но при использовании параллельного запуска тестов посоветую следующий вариант.
Потокобезопасный класс BaseSingleton с объектом Lock:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
namespace Singleton.Base { public abstract class ThreadSafeBaseSingleton<T> where T : new() { private static T instance; private static readonly object lockObject = new object(); private ThreadSafeBaseSingleton() { } public static T Instance { get { if (instance == null) { lock (lockObject) { if (instance == null) { instance = new T(); } } } return instance; } } } } |
В этом случае, если другой поток попытается зайти в код, ограниченный lock функцией, он будет ждать, пока объект не высвободится:
|
1 2 3 4 5 6 7 |
lock (lockObject) { if (instance == null) { instance = new T(); } } |
Потокобезопасный класс BaseSingleton с неявной (Lazy) инициализацией:
Обобщенный встроенный в платформу .Net класс Lazy<T> дает возможность проводить неявную инициализацию. Он позволяет обезопасить также и объект Т:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
using System; namespace Singleton.Base { public abstract class ThreadSafeLazyBaseSingleton<T> where T : new() { private static readonly Lazy<T> lazy = new Lazy<T>(() => new T()); public static T Instance { get { return lazy.Value; } } } } |
И наконец-то более сложный вариант со вложенными классами и рефлексией:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
using System; using System.Reflection; namespace Singleton.Base { public abstract class ThreadSafeNestedContructorsBaseSingleton<T> { public static T Instance { get { return SingletonFactory.Instance; } } internal static class SingletonFactory { internal static T Instance; static SingletonFactory() { CreateInstance(typeof(T)); } public static T CreateInstance(Type type) { ConstructorInfo[] ctorsPublic = type.GetConstructors(BindingFlags.Instance | BindingFlags.Public); if (ctorsPublic.Length > 0) { throw new Exception(string.Concat(type.FullName, " has one or more public constructors so the property cannot be enforced.")); } ConstructorInfo nonPublicConstructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[0], new ParameterModifier[0]); if (nonPublicConstructor == null) { throw new Exception(string.Concat(type.FullName, " does not have a private/protected constructor so the property cannot be enforced.")); } try { return Instance = (T)nonPublicConstructor.Invoke(new object[0]); } catch (Exception e) { throw new Exception( string.Concat("The Singleton could not be constructed. Check if ", type.FullName, " has a default constructor."), e); } } } } } |
Используя это вариант, классы BasePage теперь не будут иметь конструкторов, наследуя шаблон Singleton.
Теперь структура базовых классов будет немного изменена:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
using Singleton.Base; namespace Singleton.Core { public abstract class BasePageSingleton<S, M> : ThreadSafeNestedContructorsBaseSingleton<S> where M : BasePageElementMap, new() where S : BasePageSingleton<S, M> { protected M Map { get { return new M(); } } public virtual void Navigate(string url = "") { Driver.Browser.Navigate().GoToUrl(string.Concat(url)); } } public abstract class BasePageSingleton<S, M, V> : BasePageSingleton<S, M> where M : BasePageElementMap, new() where V : BasePageValidator<M>, new() where S : BasePageSingleton<S, M, V> { public V Validate() { return new V(); } } } |
Заметьте, что классы являются абстрактными, наследуясь от ThreadSafeNestedContructorsBaseSingleton и не реализовывая его методы и свойства.
Класс объектов теперь наследуется от него:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
using Singleton.Core; namespace Singleton.BingMainPageSingletonDerived { public class BingMainPage : BasePageSingleton<BingMainPage, BingMainPageElementMap, BingMainPageValidator> { private BingMainPage() { } public void Search(string textToType) { this.Map.SearchBox.Clear(); this.Map.SearchBox.SendKeys(textToType); this.Map.GoButton.Click(); } public override void Navigate(string url = "http://www.bing.com/") { base.Navigate(url); } } } |
Хочу обратить внимание, что конструктор этого класса теперь является приватным, что предостерегает нас от использования конструктора по-умолчанию.
Теперь создание объектов страниц осуществляется без использования ключевого слова new():
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
using Microsoft.VisualStudio.TestTools.UnitTesting; using Singleton.BingMainPageSingletonDerived; using S = Singleton.BingMainPageSingletonDerived; using Singleton.Core; using System.Threading; namespace Singleton { [TestClass] public class BingSingletonTests { [TestInitialize] public void SetupTest() { Driver.StartBrowser(); } [TestCleanup] public void TeardownTest() { Driver.StopBrowser(); } [TestMethod] public void SearchTextInBing_Advanced_PageObjectPattern_Singleton() { S.BingMainPage.Instance.Navigate(); S.BingMainPage.Instance.Search("автоматизированное тестирование"); S.BingMainPage.Instance.Validate().ResultsCount("РЕЗУЛЬТАТЫ: 20,900"); } } } |
На этом все. До новых статей ![]()