4

我正在尝试编写 GameBoy 模拟器,但我不确定我应该如何测试我的 CPU_LR39502 类。为了避免巨大的 if-else-if / switch-case 语句,我想出了将操作码仿函数放入 map 的想法,它以操作码为键:

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 为例),并在 lambda 中调用它 - 我不确定这是否是正确的做法。

目前,为了测试一些实现,我有这样的东西(使用谷歌测试框架):

#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 方面的经验完全为零,而且我的直觉告诉我必须有更好的方法来做到这一点。任何线索将不胜感激:)

4

2 回答 2

4

如何在不编写 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 );
}
于 2014-12-07T04:14:57.387 回答
1

可能不是您要寻找的答案,但您可以有条件地将 cpu 参数公开以进行测试

class CPU_LR35902
{    
...
public:
...
#ifndef TESTING_CPU
private:
#endif
...
};
于 2014-12-07T01:24:06.037 回答