本周小贴士#122: 测试固定装置,清晰度和数据流
Posted -飞鹤-
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了本周小贴士#122: 测试固定装置,清晰度和数据流相关的知识,希望对你有一定的参考价值。
作为totW#122最初发表于2016年8月30日
由Titus Winters (titus@google.com)创作
更新于2017年10月20日
清晰地表达模糊不清——E.B.怀特
测试代码与生产代码有何不同?一方面,测试是未经测试的:当你编写散布在多个文件中并且有数百行SetUp的意大利面条样的代码时,任何人如何确定测试确实是需要测试的内容?你的代码审查员经常不得不假设设置是有意义的,并且尽量抽查每个独立测试用例的逻辑。在这些情况下,如果某些事情发生改变,那么你的测试很可能会失败,但是很少有人知道那件事是否正确。
另一方面,如果你让每个测试尽可能简单和直接,那么通过检视,理解逻辑和审查更高质量的测试逻辑将更容易看出它是正确的。让我们看几个简单的方式来达到这一点。
在设施中的数据流
考虑如下示例:
class FrobberTest : public ::testing::Test
protected:
void ConfigureExampleA()
example_ = "Example A";
frobber_.Init(example_);
expected_ = "Result A";
void ConfigureExampleB()
example_ = "Example B";
frobber_.Init(example_);
expected_ = "Result B";
Frobber frobber_;
string example_;
string expected_;
;
TEST_F(FrobberTest, CalculatesA)
ConfigureExampleA();
string result = frobber_.Calculate();
EXPECT_EQ(result, expected_);
TEST_F(FrobberTest, CalculatesB)
ConfigureExampleB();
string result = frobber_.Calculate();
EXPECT_EQ(result, expected_);
在这个相当简单的示例中,我们的测试横跨30行代码。很容易想象不那么简单的示例是10倍于此:肯定比任何单个屏幕都适合。想验证代码是正确的阅读者或审查员必须扫描如下:
- “好的,这是一个FrobberTest,它在哪里定义的…哦,这个文件。太好了。"
- "ConfigureExampleA…这是一个FrobberTest方法。它操作一些成员变量。那些是什么类型?它们是如何初始化的?好的,Frobber和两个字符串。有设置吗?好的,默认构造。”
- “回到测试:好的,我们计算一个结果并将其与预期值进行比较…我们又在那里存储了什么?”
比较这更简单风格的等价代码:
TEST(FrobberTest, CalculatesA)
Frobber frobber;
frobber.Init("Example A");
EXPECT_EQ(frobber.Calculate(), "Result A");
TEST(FrobberTest, CalculatesB)
Frobber frobber;
frobber.Init("Example B");
EXPECT_EQ(frobber.Calculate(), "Result B");
使用这种风格,即便在有数百个测试的世界中,我们也能通过仅有的本地信息弄清晰发生了什么。
首选自由函数
在上个示例中,所有的变量初始化是好的简洁的。在真实测试中,这并不一定是现实的。然而,关于数据流和避免固定装置的相同想法可能更适用。考虑这个protebuf的示例:
class BobberTest : public ::testing::Test
protected:
void SetUp() override
bobber1_ = PARSE_TEXT_PROTO(R"(
id: 17
artist: "Beyonce"
when: "2012-10-10 12:39:54 -04:00"
price_usd: 200)");
bobber2_ = PARSE_TEXT_PROTO(R"(
id: 21
artist: "The Shouting Matches"
when: "2016-08-24 20:30:21 -04:00"
price_usd: 60)");
BobberProto bobber1_;
BobberProto bobber2_;
;
TEST_F(BobberTest, UsesProtos)
Bobber bobber(bobber1_, bobber2_);
SomeCall();
EXPECT_THAT(bobber.MostRecent(), EqualsProto(bobber2_));
再次,集中式重构会导致许多间接性:声明和初始化是分开的,并且与实际使用相距甚远。此外,由于中间的SomeCall()以及我们使用固定装置和固定装置成员变量,如果不检查详细信息,就无法确定在初始化和 EXPECT_THAT 验证之间没有修改 bobber1_ 和 bobber2_ SomeCall() 的。 可能需要更多的滚动。
考虑改为:
BobberProto RecentCheapConcert()
return PARSE_TEXT_PROTO(R"(
id: 21
artist: "The Shouting Matches"
when: "2016-08-24 20:30:21 -04:00"
price_usd: 60)");
BobberProto PastExpensiveConcert()
return PARSE_TEXT_PROTO(R"(
id: 17
artist: "Beyonce"
when: "2012-10-10 12:39:54 -04:00"
price_usd: 200)");
TEST(BobberTest, UsesProtos)
Bobber bobber(PastExpensiveConcert(), RecentCheapConcert());
SomeCall();
EXPECT_THAT(bobber.MostRecent(), EqualsProto(RecentCheapConcert()));
将初始化移动到自由函数中可以清楚地表明没有隐藏的数据流。为辅助取一个好的名字意味着你可能无需向上滚动去查看辅助的详细信息以确定测试的正确性。
五个简单的步骤
你通常可以通过下面的步骤来提升测试的清晰度:
- 在合适的情况下避免使用固定装置。有时不是的。
- 如果你使用固定装置,请尝试避免固定的成员变量。以类似全局变量的方式开始操作它们太容易了:数据流难以追踪,因为设置中的任何代码路径都可能修改成员。
- 如果你有需要复杂初始化的变量,这会使每个测试难以阅读,请考虑一个辅助函数(不是固定设置的一部分),它描述初始化并且直接返回对象。
- 如果你必须拥有包含成员变量的固定装置,请尽量避免直接操作成员的方法:尽可能将它们作为参数传递以使数据流更清晰。
- 尝试在编写头文件之前编写测试:如果你从一个易于测试的用法开始,那么你的API通常更好,并且你的测试几乎总是更清晰。
以上是关于本周小贴士#122: 测试固定装置,清晰度和数据流的主要内容,如果未能解决你的问题,请参考以下文章