Пишем эмулятор Gameboy на C++, как тестировать коды операций (Google Test Framework)?

Я пытаюсь написать эмулятор GameBoy, но не знаю, как мне протестировать мой класс CPU_LR39502. Чтобы избежать громоздких операторов if-else-if / switch-case, мне пришла в голову идея поместить функтор кода операции в карту, которая принимает код операции в качестве ключа:

class Functor
{
    std::function<void()> m_function;
public:
    Functor(std::function<void()>&& function)
    {
        m_function = std::move(function);
    }

    void operator()()
    {
        m_function();
    }
};

class BaseOpcodeFunctor : public Functor
{
    unsigned char m_opcode;
    std::string m_disasmString;
public:
    BaseOpcodeFunctor(std::function<void()>&& function,
                      unsigned char opcode,
                      std::string&& disasmString)
        : Functor(std::move(function)),
          m_opcode(opcode),
          m_disasmString(std::move(disasmString)) {}

    std::string disasm()
    {
        return m_disasmString;
    }

    unsigned char getAssignedOpcode()
    {
        return m_opcode;
    }

};

И пример этого:

class CPU_LR35902
{    
    ...
    std::map<unsigned char, BaseOpcodeFunctor> m_baseOpcodeMap;

public:
    CPU_LR35902()
    {
        ...
        initializeBaseOpcodeMap();
    }
    ...

private:
    void addFunctorToBaseOpcodeMap(BaseOpcodeFunctor&& functor);
    void initializeBaseOpcodeMap()
    {
        ...
        addFunctorToBaseOpcodeMap(BaseOpcodeFunctor([this]() {
            bitwiseRotationLeft(REGISTER_A);
        }, 0x07, "RLCA"));
    }
    void bitwiseRotationLeft(LR35902_8BIT_REGISTERS reg)
    {
        resetFlag(FLAG_Z);
        resetFlag(FLAG_N);
        resetFlag(FLAG_H);
        setFlag(FLAG_C, registers_8bit.at(reg) >> 7);
        registers_8bit.at(reg) <<= 1;
        registers_8bit.at(reg) |= getFlag(FLAG_C);
    }
    ...
};

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

В настоящее время для тестирования некоторых реализаций у меня есть что-то вроде этого (с использованием тестовой среды Google):

#include "cpu_lr35902.h"
#include <gtest/gtest.h>

class CPUTest : public ::testing::Test
{
protected:
    CPU_LR35902 cpu_testable;
};

TEST_F(CPUTest, test_bitwiseRotationLeft)
{
    cpu_testable.flags = 0;
    cpu_testable.clearRegisters();

    //0xA5 = 1010 0101, after: 0100 1011 = 0x4B
    cpu_testable.registers_8bit.at(CPU_LR35902::REGISTER_A) = 0xA5;
    cpu_testable.bitwiseRotationLeft(CPU_LR35902::REGISTER_A);
    ASSERT_EQ(1, cpu_testable.getFlag(CPU_LR35902::FLAG_C));
    ASSERT_EQ(0x4B, cpu_testable.registers_8bit.at(CPU_LR35902::REGISTER_A));

}

но чтобы получить доступ к закрытым членам CPU_LR35902, я должен добавить

FRIEND_TEST(CPUTest, test_name);

в классе CPU_LR35902 — после этого я могу получить доступ к закрытым членам тестируемого класса в TEST_F, но не могу получить к ним доступ в классе CPUTest (для SetUp/TearDown). Учитывая тот факт, что тестов у меня немного больше и их будет много, я думаю, что добавление FRIEND_TEST для каждого теста делает все как-то некрасиво. Я некоторое время знаком с C++, но у меня совершенно нет опыта использования Google Test Framework, и моя интуиция подсказывает мне, что должен быть лучший способ сделать это. Любые подсказки будут с радостью оценены :)


person pablo432    schedule 06.12.2014    source источник


Ответы (2)


Как протестировать участников закрытого класса без написания FRIEND_TEST()?

Напишите тесты как члены класса приспособления:

class Foo {
  friend class FooTest;
  ...
};

class FooTest : public ::testing::Test {
 protected:
  ...
  void Test1() {...} // This accesses private members of class Foo.
  void Test2() {...} // So does this one.
};

TEST_F(FooTest, Test1) {
  Test1();
}

TEST_F(FooTest, Test2) {
  Test2();
}

Это делает так, что вам нужно только добавить один класс для каждого тестового прибора, без необходимости включать gtest в ваш заголовок (или ваш проект).

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

class Foo {
  friend class FooTesting;
  ...
};

class FooTesting {
 public:

  static int read_private_variable1( Foo& );
};

TEST_F(FooTest, Test1) {
  Foo bar;
  EXPECT_EQ( FooTesting::read_private_variable1( bar ), 5 );
}
person mukunda    schedule 07.12.2014

Вероятно, это не тот ответ, который вы ищете, но вы можете условно сделать параметры процессора общедоступными для тестирования.

class CPU_LR35902
{    
...
public:
...
#ifndef TESTING_CPU
private:
#endif
...
};
person yogi    schedule 07.12.2014
comment
Спасибо за предложение, но я почти уверен, что это тоже неправильный подход. - person pablo432; 07.12.2014