Закрытый метод поблочного тестирования в ресурсе руководящий класс (C++)

Я ранее задал этот вопрос под другим именем, но удалил его, потому что я не объяснил это очень хорошо.

Скажем, у меня есть класс, который управляет файлом. Скажем, то, что этот класс рассматривает файл как наличие определенного формата файла и содержит методы для выполнения операций на этом файле:

class Foo {
    std::wstring fileName_;
public:
    Foo(const std::wstring& fileName) : fileName_(fileName)
    {
        //Construct a Foo here.
    };
    int getChecksum()
    {
        //Open the file and read some part of it

        //Long method to figure out what checksum it is.

        //Return the checksum.
    }
};

Скажем, я хотел бы смочь к модульному тесту часть этого класса, который вычисляет контрольную сумму. Поблочное тестирование части класса, которые загружаются в файле и таком, непрактично, потому что протестировать каждую часть getChecksum() метод я, возможно, должен был бы создать 40 или 50 файлов!

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

class Foo {
    std::wstring fileName_;
    static int calculateChecksum(const std::vector<unsigned char> &fileBytes)
    {
        //Long method to figure out what checksum it is.
    }
public:
    Foo(const std::wstring& fileName) : fileName_(fileName)
    {
        //Construct a Foo here.
    };
    int getChecksum()
    {
        //Open the file and read some part of it

        return calculateChecksum( something );
    }
    void modifyThisFileSomehow()
    {
        //Perform modification

        int newChecksum = calculateChecksum( something );

        //Apply the newChecksum to the file
    }
};

Теперь я хотел бы к модульному тесту calculateChecksum() метод, потому что легко протестировать и сложный, и я не забочусь о поблочном тестировании getChecksum() потому что это просто и очень трудно протестировать. Но я не могу протестировать calculateChecksum() непосредственно, потому что это private.

Кто-либо знает о решении этой проблемы?

6
задан Billy ONeal 12 February 2010 в 17:59
поделиться

6 ответов

В принципе, похоже, что вам нужен mock, чтобы сделать модульное тестирование более осуществимым. Способ сделать класс пригодным для модульного тестирования независимо от иерархии объектов и внешних зависимостей - это инъекция зависимостей. Создайте класс "FooFileReader" следующим образом:

class FooFileReader
{
public:
   virtual std::ostream& GetFileStream() = 0;
};

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

Теперь сделайте конструктор foo с такой сигнатурой:

Foo(FooFileReader* pReader)

Теперь вы можете создать foo для модульного тестирования, передав объект-макет, или создать его с реальным файлом, используя реализацию, которая открывает файл. Заверните создание "настоящего" Foo в фабрику, чтобы клиентам было проще получить правильную реализацию.

Используя этот подход, нет причин не тестировать на " int getChecksum()", поскольку его реализация теперь будет использовать объект-макет.

2
ответ дан 16 December 2019 в 21:39
поделиться

Один из способов - выделить метод контрольной суммы в его собственный класс и иметь открытый интерфейс для тестирования.

3
ответ дан 16 December 2019 в 21:39
поделиться

Простой и прямой ответ - сделать класс модульного тестирования другом тестируемого класса. Таким образом, класс модульного тестирования может получить доступ к calculateChecksum () , даже если он частный.

Другая возможность состоит в том, что у Foo, по-видимому, есть ряд не связанных между собой обязанностей, и, возможно, он подлежит рефакторингу. Вполне возможно, что вычисление контрольной суммы вообще не должно быть частью Foo . Вместо этого, вычисление контрольной суммы может быть лучше в качестве алгоритма общего назначения, который каждый может применять по мере необходимости (или, возможно, что-то вроде обратного - функтор для использования с другим алгоритмом, таким как std :: accumulate ).

1
ответ дан 16 December 2019 в 21:39
поделиться

Я бы начал с извлечения кода вычисления контрольной суммы в его собственный класс:

class CheckSumCalculator {
    std::wstring fileName_;

public:
    CheckSumCalculator(const std::wstring& fileName) : fileName_(fileName)
    {
    };

    int doCalculation()
    {
        // Complex logic to calculate a checksum
    }
};

Это позволяет очень легко протестировать вычисление контрольной суммы изолированно. Однако вы можете сделать еще один шаг и создать простой интерфейс:

class FileCalculator {

public:
    virtual int doCalculation() =0;
};

И реализация:

class CheckSumCalculator : public FileCalculator {
    std::wstring fileName_;

public:
    CheckSumCalculator(const std::wstring& fileName) : fileName_(fileName)
    {
    };

    virtual int doCalculation()
    {
        // Complex logic to calculate a checksum
    }
};

А затем передать интерфейс FileCalculator вашему конструктору Foo :

class Foo {
    std::wstring fileName_;
    FileCalculator& fileCalc_;
public:
    Foo(const std::wstring& fileName, FileCalculator& fileCalc) : 
        fileName_(fileName), 
        fileCalc_(fileCalc)
    {
        //Construct a Foo here.
    };

    int getChecksum()
    {
        //Open the file and read some part of it

        return fileCalc_.doCalculation( something );
    }

    void modifyThisFileSomehow()
    {
        //Perform modification

        int newChecksum = fileCalc_.doCalculation( something );

        //Apply the newChecksum to the file
    }
};

В своем реальном производственном коде вы должны создать CheckSumCalculator и передать его Foo , но в коде модульного теста вы можете создать Fake_CheckSumCalculator (это для example всегда возвращал известную предопределенную контрольную сумму).

Теперь, хотя Foo зависит от CheckSumCalculator , вы можете создавать и тестировать эти два класса в полной изоляции.

1
ответ дан 16 December 2019 в 21:39
поделиться
#ifdef TEST
#define private public
#endif

// access whatever you'd like to test here
1
ответ дан 16 December 2019 в 21:39
поделиться

Что ж, предпочтительный способ в C ++ для файлового ввода-вывода - это поток. Таким образом, в приведенном выше примере было бы гораздо больше смысла вводить поток вместо имени файла. Например,

Foo(const std::stream& file) : file_(file)

Таким образом, вы можете использовать std :: stringstream для модульного тестирования и иметь полный контроль над тестом.

Если вы не хотите использовать потоки, можно использовать стандартный пример шаблона RAII, определяющего класс File . Тогда «простой» способ продолжить - создать чистый виртуальный интерфейсный класс , файл , а затем реализацию интерфейса. Тогда класс Foo будет использовать интерфейсный класс File. Например,

Foo(const File& file) : file_(file)

Тестирование затем выполняется путем простого создания простого подкласса для файла и его внедрения (заглушка). Также можно создать фиктивный класс (см., Например, Google Mock).

Однако вы, вероятно, захотите также провести модульное тестирование класса реализации File , и, поскольку это RAII, ему, в свою очередь, потребуется некоторая инъекция зависимостей. Обычно я пытаюсь создать чистый виртуальный интерфейсный класс, который просто обеспечивает основные операции с файлом C (открытие, закрытие, чтение, запись и т. Д. Или fopen, fclose, fwrite, fread и т. Д.). Например,

class FileHandler {
public:
    virtual ~FileHandler() {}
    virtual int open(const char* filename, int flags) = 0;
    // ... and all the rest
};

class FileHandlerImpl : public FileHandlerImpl {
public:
    virtual int open(const char* filename, int flags) {
        return ::open(filename, flags);
    }
    // ... and all the rest in exactly the same maner
};

Этот класс FileHandlerImpl настолько прост, что я не тестирую его. Однако преимущество состоит в том, что, используя его в конструкторе класса FileImpl , я могу легко выполнить модульное тестирование класса FileImpl . Например,

FileImple(const FileHandler& fileHandler, const std::string& fileName) : 
    mFileHandler(fileHandler), mFileName(fileName)

Единственным недостатком на данный момент является необходимость передачи FileHandler . Я подумал об использовании интерфейса FileHandle для фактического предоставления статических экземпляров set / get-методов, которые можно использовать для получения одного глобального экземпляра объекта FileHandler . Хотя на самом деле это не синглтон и, следовательно, все еще можно тестировать, это не изящное решение. Думаю, сейчас лучший вариант - передать хендлера.

0
ответ дан 16 December 2019 в 21:39
поделиться
Другие вопросы по тегам:

Похожие вопросы: