Как сделать существующий общедоступный API тестируемым для внешних программистов, использующих его? - PullRequest
6 голосов
/ 23 декабря 2008

У меня есть общедоступный API C #, который используется многими сторонними разработчиками, написавшими поверх него собственные приложения. Кроме того, API широко используется внутренними разработчиками.

Этот API не был написан с учетом тестируемости: большинство методов класса не являются виртуальными, и вещи не были выделены в интерфейсы. Кроме того, есть несколько вспомогательных статических методов.

По многим причинам я не могу существенно изменить дизайн, не вызывая серьезных изменений для приложений, разработанных программистами, использующими мой API. Однако я все же хотел бы дать внутренним и внешним разработчикам, использующим этот API, возможность писать модульные тесты и иметь возможность имитировать объекты в API.

Есть несколько подходов, которые приходят на ум, но ни один из них не кажется великолепным:

  1. Традиционный подход состоит в том, чтобы заставить разработчиков создавать прокси-класс, которым они управляют, который будет взаимодействовать с моим API. На практике это не сработает, потому что в настоящее время существуют сотни классов, многие из которых являются эффективно строго типизированными объектами передачи данных, которые было бы затруднительно воспроизводить и поддерживать.

  2. Заставить всех разработчиков, использующих API, которые хотят провести модульное тестирование, купить TypeMock . Это кажется резким, заставляя людей платить более 300 долларов за разработчика и потенциально требовать от них изучения другого инструмента для имитации объектов, чем тот, к которому они привыкли.

  3. Пройдите весь проект и сделайте все методы виртуальными. Это позволило бы макетировать объекты с использованием бесплатных инструментов, таких как Moq или Rhino Mocks , но это потенциально могло бы открыть риски безопасности для классов, которые никогда не были получены из. Кроме того, это может привести к серьезным изменениям.

  4. Я мог бы создать инструмент, который бы давал входную сборку, которая выводила бы сборку с теми же пространствами имен, классами и членами, но делала бы все методы виртуальными, а тело метода просто возвращало бы значение по умолчанию для типа возврата. Затем я мог отправлять эту фиктивную тестовую сборку каждый раз, когда выпускал обновление API. Затем разработчики могли бы написать тесты для API против фиктивной сборки, так как у нее были виртуальные члены, которые очень поддаются моделированию. Это может работать, но кажется немного утомительным, чтобы написать собственный инструмент для этого, и я не могу найти существующий, который делает это хорошо (особенно это хорошо работает с дженериками). Кроме того, оно усложняется тем, что разработчикам необходимо использовать две разные сборки, которые могут устареть.

  5. Подобно # 4, я мог бы пройтись по каждому файлу и добавить что-то вроде "#ifdef UNITTEST" в каждый метод и тело, чтобы сделать эквивалент того, что сделал бы инструмент. Для этого не требуется внешний инструмент, но он загрязнит базу кода множеством уродливых "#ifdef".

Есть ли что-то еще, что я не считаю подходящим? Инструмент, подобный тому, что я упомянул в # 4, уже существует?

Опять же, сложным фактором является то, что это довольно большой API (сотни классов и ~ 10 файлов), в котором используются существующие приложения, что затрудняет внесение радикальных изменений в проект.

Там было было несколько вопросов о переполнении стека, которые были общими для модернизации существующего приложения, чтобы сделать его тестируемым, но ни одного Похоже, я решаю проблемы (особенно в контексте широко используемого API со многими сторонними разработчиками). Я также осведомлен о « эффективной работе с устаревшим кодом » и думаю, что в нем есть хороший совет, но я ищу конкретный подход .net с учетом упомянутых выше ограничений.

ОБНОВЛЕНИЕ: Я ценю ответы до сих пор. Вот что поднял Патрик Хагне : «Почему бы не извлечь интерфейсы?» Это действительно работает до некоторой степени, но имеет некоторые проблемы, такие как существующий дизайн имеет много случаев, когда мы выставляем конкретный класс. Например:

public class UserRepository 
{ 
    public UserData GetData(string userName) 
    {
        ...
    } 
}

Существующие клиенты, ожидающие конкретного класса (например, «UserData»), сломались бы, если бы им дали «IUserData».

Кроме того, как уже упоминалось в комментариях, есть случаи, когда мы берем класс, а затем выставляем его для удобства. Это может вызвать проблемы, если мы возьмем интерфейс и затем представим его как конкретный класс.

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

У нас есть тестирование текущего API, но это более сложное интеграционное тестирование, чем модульное тестирование.

Кроме того, как уже упоминалось Чед Майерс , этот вопрос решает аналогичную проблему, с которой сама платформа .NET сталкивается в некоторых областях.

Я понимаю, что я, вероятно, ищу здесь «серебряную пулю», которой не существует, но вся помощь приветствуется. Важной частью является защита огромных временных вложений многих сторонних разработчиков, а также огромной существующей разработки для создания текущего API.

Все ответы, особенно те, которые рассматривают деловую сторону проблемы, будут тщательно рассмотрены. Спасибо!

Ответы [ 6 ]

4 голосов
/ 23 декабря 2008

На самом деле вы спрашиваете: «Как мне разработать мой API с учетом SOLID и схожих принципов, чтобы мой API хорошо сочетался с другими?» Дело не только в тестируемости. Если у ваших клиентов возникают проблемы при тестировании своего кода вместе с вашим, то у них также возникают проблемы при написании / использовании своего кода вместе с вашим, так что это большая проблема, чем просто тестируемость.

Простое извлечение интерфейсов не решит проблему, потому что, скорее всего, ваши существующие интерфейсы классов (которые конкретные классы представляют как их методы / свойства) не разработаны с учетом принципа разделения интерфейсов, поэтому извлеченный интерфейс будет иметь все виды проблемы (некоторые из которых вы упомянули в комментарии к предыдущему ответу).

Мне нравится называть это проблемой IHttpContext. ASP.NET, как вы знаете, очень сложно протестировать или из-за проблемы «Волшебная зависимость от одиночного» в HttpContext.Current. HttpContext не может быть издевательским без хитрых трюков, подобных тому, что использует TypeMock. Простое извлечение интерфейса HttpContext не сильно поможет, потому что оно ТАК огромно. В конце концов, даже IHttpContext станет таким бременем для тестирования, что почти ничего не стоит делать, кроме попыток издеваться над самим HttpContext.

Определение обязанностей объекта, надлежащее разделение интерфейсов и взаимодействий, а также проектирование с учетом принципа «открытый / закрытый» - это не то, что вы пытаетесь заставить / втиснуть в существующий API, разработанный без учета этих принципов.

Я не хочу оставлять вас с таким мрачным ответом, поэтому я дам вам одно положительное предложение: как насчет того, чтобы ВЫ взяли на себя все горе от имени своих клиентов и сделали какой-то слой обслуживания / фасада поверх старого? API. Этот уровень обслуживания должен учитывать мелочи и трудности вашего API, но он будет представлять собой хороший, чистый, SOLID-дружественный публичный API, который ваши клиенты смогут использовать с гораздо меньшими трудностями.

Это также дает дополнительное преимущество, заключающееся в том, что вы можете медленно заменять части вашего API и в конечном итоге сделать его таким, чтобы ваш новый API был не просто фасадом, а ИС (а старый API постепенно сокращается).

2 голосов
/ 23 декабря 2008

Другой подход - создать отдельную ветку API и сделать там вариант 3. Затем вы просто поддерживаете эти две версии и осуждаете первую. Слияние изменений из одной ветви в другую должно работать автоматически большую часть времени.

1 голос
/ 23 декабря 2008

В ответ на ваше редактирование, извлечение интерфейса действительно очень хорошо работает здесь:

public interface IUserRepository
{
    IUserData GetData(string userName);
}

public class UserRepository 
    : IUserRepository
{
    // The old method is not touched.
    public UserData GetData(string userName)
    {
        ...    
    }

    // Explicitly implement the interface method.
    IUserData IUserRepository.GetData(string userName)
    {
        return this.GetData(userName);
    }
}

Как я также сказал в комментарии, это не может быть путь в каждом месте. Я думаю, что вы должны определить некоторые основные моменты в вашем API, где для ваших клиентов очень важно иметь возможность подделать взаимодействие и начать с него Вам не нужно полностью переписывать весь API, но он может постепенно преобразовываться.

0 голосов
/ 27 декабря 2008

Я согласен с Ким. Почему бы не переписать ваш основной API, используя лучшие методики, которые вы объяснили, и предоставить набор классов прокси / адаптеров, которые предоставляют старый интерфейс, но взаимодействуют с вашим новым API?

Старые разработчики, естественно, будут поощряться к переходу на новый API, но не будут вынуждены делать это немедленно. Новые разработчики будут просто использовать ваш новый API. Объявите EOL для вашего старого интерфейса API, если вы беспокоитесь о разработчиках, использующих старый API.

0 голосов
/ 23 декабря 2008

Сторонние пользователи не должны тестировать ваш API. Они хотели бы проверить свой код на соответствие вашему API, поэтому им необходимо создать Mocks API и т. Д., Но они будут полагаться на ваше тестирование API, чтобы убедиться, что оно работает. Или ты это имел ввиду? Вы хотите, чтобы ваш API легко тестировался?

Начните снова в этом случае, и на этот раз подумайте о тестерах:)

0 голосов
/ 23 декабря 2008

Один из подходов, который вы не упоминаете (и который я бы предпочел в большинстве случаев), - это извлечение интерфейсов для классов, которые вы хотите, чтобы пользователь API мог имитировать. Не зная вашего API, не каждый класс в нем должен извлекать свой интерфейс.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...