Я пытаюсь проверить код устаревшего кода на C++. В частности, у меня есть иерархия классов, скажем, A < B < C
(то есть A
является подклассом B
, а B
является подклассом C
), и существует глобальная ссылка на объект типа C
, который используется со всех сторон код системы (singleton pattern). Цель состоит в том, чтобы заменить объект C
на некоторый поддельный объект (фактически, для доступа к базе данных используется C
).Как подделывать классы C++, содержащие не виртуальные функции?
Моя первая попытка была ввести интерфейсы IA, IB, and IC
(которые содержат чистые виртуальные версии функций соответствующего класса), пусть каждый класс реализовать свой интерфейс, и изменить тип глобальной C
ссылкой на IC
. В настройке моих тестов я бы затем заменил объект C
, связанный глобально с моей собственной реализацией IC
, что сделало всю систему используемой моей поддельной реализацией.
Однако классы A, B
и C
содержат в себе немало не виртуальных функций. Теперь, если я сделаю классы наследуемыми от своих интерфейсов, я бы изменил семантику этих функций от не виртуального к виртуальному (Перья обсуждают эту проблему в «Эффективной работе с устаревшим кодом», с.367). Другими словами: я должен проверять каждый вызов на мой глобальный объект, и я должен убедиться, что после моих изменений все же будут вызываться одни и те же функции. Это звучит, как много работы ERROR PRONE для меня.
Я также думал о том, чтобы не-виртуальных функциях «окончательные», то есть сказать компилятору, что функции A, B
и C
не должны быть скрыты в подклассах (который сделал бы составитель сказать мне все потенциально опасные функции B
и C
- если функция не скрыта в базовом классе, вышеупомянутый эффект вообще не может произойти), но это, похоже, не поддерживается C++ (мы еще не используем C++ 11, но даже его окончательный ключевое слово только кажется применимым к виртуальным функциям).
Чтобы сделать ситуацию еще более сложной, классы A, B
и C
также содержат общедоступные атрибуты, виртуальные функции, а также некоторые функции шаблона.
Так что мой вопрос: как справиться с ситуацией, описанной выше? Есть ли возможности C++, которые я пропустил, и которые могут помочь в моем сценарии? Любые шаблоны проектирования? Или даже инструменты рефакторинга? Мое основное требование состоит в том, что изменения должны быть максимально безопасными, поскольку классы, которые я хотел бы подделать, имеют очень важное значение для системы ... Я также был бы доволен «уродливым» решением, которое позволило бы мне поставить тесты (и которые могут быть реорганизованы позже, если система надлежащим образом покрыта испытаниями).
Редактировать: Я испортил свою иерархию наследования (она перевернулась) - это исправлено сейчас.
Редакция 2: Мы, наконец, закончили следующим образом: мы сделали только виртуальные функции, которые нам действительно нужны для наших текущих тестовых случаев. Затем мы проверили каждый вызов на эти методы (который был управляемым). Это позволило нам насмехаться над нашим классом с помощью Google Mocks. Имея все больше и больше тестовых примеров, мы надеемся, что наши изменения будут сохраняться со временем. Обратите внимание, что, задавая свой вопрос, я думал, что Google Mocks может только издеваться над чистыми интерфейсами; это не корпус, что позволяет использовать инкрементный подход, как описано выше.
Как правило, проблема номер один с тестированием - это глобальные объекты, поэтому я предлагаю избавиться от этого, прежде чем делать что-либо еще. – Puppy
Являются ли классы 'A' и/или' B' конкретными? Если да, то они должны * быть? –
@Puppy: наша система имеет довольно много глобальных объектов, и я рассматриваю это как типичную проблему с курицей и яйцом: Чтобы безопасно избавиться от глобалов, мне нужны тесты, а для написания тестов мне нужно избавиться глобалы. Таким образом, я сейчас пытаюсь получить тесты на месте с минимальными и безопасными, как и всевозможными рефакторингами, и я планирую позже решить проблему с полным глобалом. – csoltenborn