Лучший способ обновить две общие функции для использования общей функции

Мастер модульного тестирования Microsoft создает объекты Accessor, если вам нужно протестировать непубличное свойство из другого проекта. В своих модульных тестах я создаю вспомогательные функции, чтобы не повторять один и тот же код только в каждом методе модульного теста. В настоящее время у меня есть два теста, которые практически идентичны, за исключением того, что один принимает стандартный открытый объект, а другой - версию Accessor. Поскольку Accessor основан на публичном объекте, у меня должна быть одна функция. Я предполагал, что смогу использовать Generics для выполнения простого броска. Но после публикации вопроса я обнаружил, что это было намного больше работы, включая необходимость обновления базовых объектов. Мой вопрос - это другой подход, чтобы свести эти избыточные методы к использованию только одной функции с использованием приведения (или другого) подхода?

Вот две существующие функции:

// Common function to create a new test record with standard Account object
internal static void CreateAccount(out Account account, bool saveToDatabase)
{
    DateTime created = DateTime.Now;
    string createdBy = _testUserName;

    account = new Account(created, createdBy);

    account.Notes = Utilities.RandomString(1000);

    if (saveToDatabase)
        account.Create();
}

// Common function to create a new test record with Account_Accessor
internal static void CreateAccount(out Account_Accessor account, bool saveToDatabase)
{
    DateTime created = DateTime.Now;
    string createdBy = _testUserName;

    account = new Account_Accessor(created, createdBy);

    account.Notes = Utilities.RandomString(1000);

    if (saveToDatabase)
        account.Create();
}

У меня есть две дюжины этих модульных тестов, а реальные объекты имеют в среднем 10 свойств, примеры которых я упростил здесь.

Вот код Accessor, который создает API модульного теста (опять же, я сократил его, чтобы упростить пример):

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.ObjectModel;
using System.Data;

namespace NameHere.Bll
{
    [Shadowing("NameHere.Bll.Account")]
    public class Account_Accessor : ProjectBase_Accessor<Account>
    {
        protected static PrivateType m_privateType;

        public Account_Accessor(PrivateObject value);
        [Shadowing(".ctor@2")]
        public Account_Accessor(DateTime created, string createdBy);

        [Shadowing("_notes")]
        public string _notes { get; set; }

        public static Account_Accessor AttachShadow(object value);

        [Shadowing("Create@0")]
        public override void Create();
    }
}

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.ComponentModel;
using System.Linq.Expressions;

namespace NameHere.Bll
{
    [Shadowing("NameHere.Bll.ProjectBase`1")]
    public class ProjectBase_Accessor<T> : BaseShadow, INotifyPropertyChanged
    {
        protected static PrivateType m_privateType;

        public ProjectBase_Accessor(PrivateObject value);

        [Shadowing("Created")]
        public DateTime Created { get; set; }
        public static PrivateType ShadowedType { get; }

        [Shadowing("add_PropertyChanged@1")]
        public void add_PropertyChanged(PropertyChangedEventHandler value);
        public static ProjectBase_Accessor<T> AttachShadow(object value);

        [Shadowing("Create@0")]
        public virtual void Create();
    }
}

1 ответ

Решение

Проблема заключается в том, что, хотя класс средства доступа предоставляет те же методы и свойства, что и класс, за которым он скрывается, нет общего интерфейса между средством доступа и исходным классом. Account_Accessor наследует от BaseShadow, Account наследует от чего-то другого. С точки зрения компилятора они являются совершенно не связанными типами, они не совместимы с присваиванием, поэтому будет сложно передать экземпляры каждого из них в общую процедуру.

Если вы могли бы заставить класс Account_Accessor реализовать тип интерфейса, который также реализован с помощью учетной записи, вы можете передать экземпляры каждого из них в функции, которые принимают тип интерфейса в качестве параметра. Как это:

internal static IAccount SetupAccount(IAccount account, bool saveToDatabase)
{
// do account setup here - not construction
}

// to call: construct instance, then pass to common function
var account = new Account(a, b);
SetupAccount(account, true);

Если создание экземпляра Account достаточно сложное, и вы хотите, чтобы для него тоже была общая подпрограмма, поместите обертки для конкретного типа перед общей функцией:

internal static IAccount CreateAccount(bool saveToDatabase)
{
    var account = new Account(a,b);
    return SetupAccount(account, saveToDatabase);
}

internal static IAccount CreateAccountAccessor(bool saveToDatabase)
{
    var account = new Account_Accessor(a,b);
    return SetupAccount(account, saveToDatabase);
}

Одна вещь, которую вы не можете избежать, заключается в следующем: кто-то где-то должен указать, какой экземпляр создать. Даже если вы сводите его к передаче типов и использования Activator.CreateInstance()кто-то должен взять на себя обязательство выбрать, какой тип использовать.

Как только экземпляр создан, и оба типа реализуют общий интерфейс, все общие функции, о которых нужно позаботиться, - это общий интерфейс.

Другие вопросы по тегам