We're sunsetting PodQuest on 2025-07-28. Thank you for your support!
Export Podcast Subscriptions
cover of episode 218: Balancing test coverage with test costs -  Nicole Tietz-Sokolskaya

218: Balancing test coverage with test costs - Nicole Tietz-Sokolskaya

2024/4/18
logo of podcast Test & Code

Test & Code

AI Deep Dive AI Insights AI Chapters Transcript
People
B
Brian
Python 开发者和播客主持人,专注于测试和软件开发教育。
N
Nicole Tietz-Sokolskaya
Topics
Nicole Tietz-Sokolskaya: 我认为软件工程师们对测试的讨论往往不够细致,常常陷入产品经理希望加快进度而工程师希望进行更多测试的僵局。我们需要重新思考测试的目的,即降低风险,并权衡测试的成本和收益,思考测试的边际效益。盲目追求代码覆盖率可能会适得其反,例如为了提高覆盖率而编写不必要的测试,或者因为代码重构而降低覆盖率,这并不一定意味着测试不足,反而可能是代码更精简的体现。我个人不再使用代码覆盖率作为衡量标准,而更关注测试的上下文和最终结果。关注测试覆盖的代码上下文比单纯的覆盖率数字更重要。并非所有代码都需要测试,有些测试的价值不高,例如对CSS代码的测试。我主要关注端到端测试的代码覆盖率,而对单元测试的代码覆盖率不太关注。在端到端测试中,处理异常情况的代码也需要测试,可以使用模拟或架构设计来模拟各种故障场景。系统对预期错误的处理能力也属于系统行为,需要进行测试。决定是否测试某种情况需要权衡其发生的可能性和后果。对于一些低概率的错误,可以设置监控告警,而不是编写大量的测试用例。决定测试哪些内容,需要考虑这些内容对业务的影响程度。应该优先测试对业务影响最大的功能模块。测试资源应该集中在对业务影响最大的功能模块上。性能测试需要模拟真实的负载情况,否则测试结果没有意义。理想情况下,性能测试应该模拟整个系统的端到端负载。可以使用监控数据来验证性能测试结果的准确性。需要权衡服务中断的成本和编写及维护测试的成本。维护测试代码的成本也很高,需要考虑在测试范围和维护成本之间取得平衡。端到端测试比单元测试更不容易受到代码重构的影响。测试套件的运行时间也是一个需要考虑的成本因素。高代码覆盖率可以帮助识别和删除无用代码。 Brian: 测试套件的运行时间不应过长,理想情况下应该在几分钟以内完成。测试套件的理想运行时间应该在几分钟以内,过长的运行时间会影响团队效率。测试套件应该模块化,以便可以快速运行和调试。测试应该分层进行,开发阶段的测试应该快速,而CI阶段的测试可以更全面。

Deep Dive

Key Insights

What is the main trade-off discussed in Nicole's blog post about testing?

The main trade-off discussed is balancing the cost of testing (time, resources, maintenance) against the risk of not testing enough (potential bugs, downtime, and business impact). The post emphasizes the need to critically evaluate how much testing is necessary and where to focus testing efforts to maximize value.

Why can refactoring code reduce code coverage percentages?

Refactoring can reduce code coverage percentages because it often results in fewer lines of code. If the same number of tests cover fewer lines, the coverage percentage drops. This creates a paradox where improving code quality by making it more concise can appear to reduce test coverage, even though the code is better.

What is the issue with aiming for 100% code coverage in a React app with styled components?

Aiming for 100% code coverage in a React app with styled components can be problematic because it requires testing every line of CSS. This often leads to low-value tests that don't meaningfully improve code reliability, while consuming significant time and effort that could be better spent on higher-impact testing.

How does Nicole suggest deciding what to test and what not to test?

Nicole suggests focusing testing efforts on the most critical parts of the system, such as features that directly impact revenue or user experience. For example, live interaction features that could result in significant financial loss if they fail should be prioritized over less critical features like analysis tools, which can tolerate occasional downtime.

What is the challenge with performance testing, according to Nicole?

Performance testing is challenging because it must closely match real-world workloads to be meaningful. Simulating realistic user behavior is difficult, especially before deployment, and testing isolated components doesn't capture the non-linear interactions between different parts of the system. Monitoring production behavior can help refine performance tests over time.

What is the cost of maintaining a large test suite?

Maintaining a large test suite can be costly because it requires ongoing effort to update tests as the codebase evolves. Refactoring becomes more difficult, and the time to run tests increases, which can slow down development workflows. Additionally, tightly coupled unit tests can break frequently during refactoring, adding to maintenance overhead.

What is Nicole's opinion on the ideal length of a test suite?

Nicole believes a test suite should ideally run in single-digit minutes, with five minutes being the upper limit for a reasonable development workflow. Longer test suites can significantly impact productivity, especially if developers get distracted while waiting for tests to complete.

How does Nicole use code coverage to improve code quality?

Nicole uses code coverage to identify and delete unreachable code. By analyzing coverage reports, she can pinpoint code that isn't being executed and remove it, which improves code quality and reduces unnecessary complexity. This approach also helps ensure that the remaining code is well-tested and functional.

Shownotes Transcript

Nicole是一位软件工程师和作家,最近撰写了关于我们在决定编写哪些测试以及多少测试才足够时所做的权衡的文章。我们讨论了: 平衡进度与测试 多少测试才是合适的测试量 是否应该衡量和跟踪代码覆盖率 良好的重构可以降低代码覆盖率 是否值得测试错误条件? 是否可以只监控罕见的错误代码? API漂移和autospec 降低风险 决定测试什么和不测试什么 将测试重点放在关键的赚钱功能上 如果代码的这一部分存在错误,会产生多少业务影响? 性能测试需要大致匹配真实世界的负载 服务中断的成本与创建、维护和运行测试的成本 保持测试套件快速以最大限度地减少分心 链接: 好东西太多:我们与测试之间的权衡 负载测试很难,而且工具……不太好。但为什么? 另一个Rust资源(YARR!) 古德哈特定律——“当一个衡量指标成为目标时,它就停止成为一个好的衡量指标” 学习pytest pytest是Python排名第一的测试框架。 使用Hello, pytest!超快速学习基础知识 之后,您可以通过《The Complete pytest Course》成为pytest专家 这两个课程都在courses.pythontest.com</context> <raw_text>0 今天的节目中,我邀请了Nicole,我们将讨论Python中的测试以及许多有趣的东西。欢迎收听《测试代码》。本期节目由HelloPyTest赞助播出,HelloPyTest是学习PyTest的最快方法,以及Python测试社区。更多信息请访问courses.pythontest.com。

欢迎,Nicole。是的,感谢你邀请我,Brian。在我们开始之前,我想介绍一下你是谁以及你做什么。是的,当然。我是Nicole Teetsakolskaya。我在网络上的大多数地方都使用N-Teets这个名字,我是一家名为Remesh的小型初创公司的高级软件工程师。因此,我大部分的工作重点是性能、扩展、后端方面,而且我

我们使用Python编写许多后端代码和机器学习代码。然后,我们还有大量的TypeScript、Go和Rust以及其他一些语言,具体取决于需要。所以现在我将时间分配在Python中的一些机器学习代码和Rust中的一些代码之间,任何需要极速运行的东西都用Rust。太棒了。酷。学习Rust是我的待办事项清单上的事情之一,Nicole有一个我们将链接的资源,名为“另一个Rust资源”。

看起来很棒。它的目标是什么?你说过它试图让人们快速入门。-是的,当我们在工作中引入Rust时,我知道学习Rust的传统途径是:这里有一本厚达一千页的Rust编程语言书籍,去读吧。我需要人们更快地掌握它。所以这个课程的目标是让你

在短短几天内就能够与了解Rust的人结对编程,然后从那里开始,在以后加深你的学习,但总的来说,只是为了减少恐惧感并让你运行起来。能够结对编程。你的组织进行结对编程吗?我们根据需要进行结对编程,主要是在

情况需要或我们遇到有趣的问题时。但作为一个远程优先的组织,我们从未真正形成一个定期进行结对编程的模式。好的。

这基本上是我对结对编程的舒适度,按需进行。酷。所以我看到了你的博客文章,名为“好东西太多:我们与测试之间的权衡”,它谈到了,它谈到了很多东西,但其中之一是平衡风险缓解和

以及基本上你想测试多少以及诸如此类的事情。那么你能为我们介绍一下关于测试和覆盖率等等的主题吗?是的。我的意思是,我认为作为软件工程师,我们对测试并没有进行非常细致的讨论,因为很多时候这归结为:产品方对测试施压,希望加快进度。工程师对加快进度施压,希望进行更多测试。

这有点像要求我们退一步思考,我们进行这些测试的真正原因是什么?我们试图从中得到什么?以及,是否存在递减收益点?我们如何知道多少测试才是合适的测试量?就像你提到的,我在文章中提到了代码覆盖率。如果我们正在衡量它,那么这样做有什么意义?以及,我们应该跟踪它吗?是的,只是简单地介绍了一些事情,但是是的。

我不知道你是否想从那里深入探讨任何特定内容。好吧,让我们来谈谈一个有时会引起争议的大问题,那就是代码覆盖率。是的。你对代码覆盖率有什么看法,或者我们应该衡量它吗?

是的。我在Vinny Flavor实习的第一份工作中做的第一件事就是在我的团队中添加代码覆盖率,然后在整个夏天提高他们的代码覆盖率。所以我是在一个非常支持代码覆盖率的文化中开始的。然后随着时间的推移,我开始注意到,当你的环境跟踪代码覆盖率时,有一种倾向是无批判地提高它,你开始做一些非常奇怪的事情,例如,如果我重构某些东西并减少其中的代码行数,但这些代码行数在许多测试中都被使用,那么你现在的代码覆盖率百分比就会降低,因为你使某些东西更小了。因此,如果你有像代码覆盖率棘轮这样的东西,那么你就会开始出现一些奇怪的副作用,例如,好吧,我重构了这个东西,但我需要在其他地方添加测试。

这样我就不会减少测试量。我认为,

那一点让我很震惊,我想,好吧,这是一个非常好的观点。所以我用一个思维练习把它推向了极端,假设我有100行代码,其中有两个50行函数。我有两个围绕它的测试。而且,由于某种原因,假设我的覆盖率只有50%。所以我覆盖了50行代码,

如果我重构它,使其更紧凑,使其成为40行代码,然后在另一个函数中添加10行代码,那么我突然就改变了,我的代码覆盖率变成了40%,即使我只是,我没有使事情变得更糟。实际上我只是使代码更紧凑了。这是一个奇怪的事情,像

是的。你不能,如果你正在减少代码行数,你不能,那么,那个,那个,你将改变覆盖率百分比。对。所以,如果你的覆盖率不是100%,那么这个数字就很难处理。是的。

我想,对于我个人控制的任何个人项目,我是一个100%代码覆盖率的人,但我大量使用例外情况。例如,如果我使用的是

第三方代码,或者即使我输入了一些代码,我知道我正在使用的代码行,我不会试图测试其他所有内容。嗯,所以我确实有一些细节,例如,这是我知道我正在使用的代码,我希望对它进行100%的覆盖。嗯,

你在之前的项目中做过这个。你仍然……我想你提到了也许这个数字和试图达到100%并不是那么好。但在文化上,你现在站在哪里?你如何使用它?是的。所以现在,我实际上并没有使用代码覆盖率。我认为它可以成为一个很好的工具来查看……

与代码更改一起集成。例如,这个更改影响的代码是否被测试覆盖?我认为这将非常有用。所以我认为,从上下文中来看,哪些代码被覆盖对我来说比原始数字更重要。但在我的项目中,我们只是没有像那样迈出一步,我们更多地关注

更多端到端的正确性、性能和其它信号。但一位朋友上周也告诉我他参与的一个项目,该项目的团队经理希望达到100%的代码覆盖率,但这是一个React应用程序,他们使用了样式组件,这意味着你的代码覆盖率达到100%也需要覆盖每一行CSS。

这就像,这里有什么有意义的测试?这也是一个奇怪的模式,就像你说的那样,必须对第三方内容或在这种情况下可能没有意义的测试内容做出例外。或者我甚至会争辩说CSS内容,你可以围绕它编写测试,但我认为这不是一个特别有价值的测试,你的精力最好花在其他地方。

是的。我必须对我的观点进行限定,我不测试任何前端内容。是的。我一直测试API。另一件事是我实际上并没有,我唯一使用代码覆盖率的地方是基于行为的端到端测试。我不认为单元测试上的代码覆盖率很有趣,因为,嗯,因为你可能看到的一些滥用情况,呃,

可能有一段代码甚至无法通过你编写的测试来访问,你永远不会知道要删除它,因为它被覆盖了,所以是的,所以在这些端到端测试中,你如何获得例如错误路径的覆盖率,当发生异常情况时,下游服务中断或发生非常奇怪的错误

在这些情况下,我认为这些特定部分的概念是模拟听起来不错。我在任何专业项目中都不使用模拟,但我将在架构中设计一个系统,我们可以模拟我们想要覆盖的所有故障。所以它就像,我不知道,在

在互联网服务的情况下,我们可以预期获得并优雅地恢复的不同错误代码的等效项。这些是你想要知道的事情。这并不是一个错误条件。这是一个行为,对吧?你,你希望你的系统能够处理。所以是的,它必须经过测试,但有些错误条件很难

自然地获得,而无需强迫。那么你如何处理它呢?我不喜欢我们所做的。我们对处理上游服务的较小单元进行测试,使用模拟,我们可以这样做。然后这很大程度上是希望你的模拟接口和你的实际内容返回类似的响应。是的。

在Python或动态类型语言中,这对我来说是一个痛点,因为我没有类型系统告诉我我的模拟是相同的。但除此之外,我的意思是,这归结为价值权衡

你认为这有多大可能?如果真的发生了,你有多在乎?所以,如果你依赖的服务历史上大部分时间都是正常的,那么也许可以假设这一点,并在很少情况下出现故障时忽略500错误,这取决于实际测试的难度。

我认为这是一个个案的基础。所以这取决于服务,对吧?或者你正在构建什么。是的。在这种情况下,我想要的是警报,这样如果发生这种情况,而不是像测试那样,好吧,我们恢复了,而只是一个警报,哦,我们从这个关键服务中获得了许多500错误。也许有人应该醒来看看。

你提到了模拟的问题,基本上是API漂移或你的模拟不匹配的问题。我不记得关键词了,但是至少使用Python单元测试模拟库,有一种方法可以……

使其与这个API匹配,它应该始终与之匹配,或者类似的东西。但我记不起这叫什么了。哦,太好了。我一定会研究一下。我在面试期间试图思考的功能当然是mock的auto spec功能。另一件我想提到的,我想是风险部分,就像我们进行测试是因为我们想要,我们想要降低风险,对吧?是的。是的。

我认为这就是原因。那么,你如何评估,你如何决定测试什么和不测试什么?是的。我的意思是,从我的专业经验来看,一个非常明显的例子是,我们工作中的平台,如果人们正在实时交互的特定部分出现故障,并且我们在不到一分钟内没有恢复,那么这可能会给我们的客户和我们自己造成巨大的经济损失。而像

分析功能,如果它们正常运行当然很好,但如果它们出现故障也没关系,因为人们可以等待。所以这不是及时的事情。这就是我们在某个时候做出故意权衡的地方,这一部分功能是我们真正真正需要知道的,如果任何东西要破坏它的话。

这就是我们投入大部分精力进行性能测试的地方,只是针对其中的所有更改进行健壮性测试。然后你会发现其他许多小错误会出现在其他部分,这些错误并不一定那么重要。显然,在整体架构中,不同部分之间会有相互作用。你不能完美地隔离它们,但我们能够根据以下情况来定位我们的工作:如果存在重大错误,哪一部分对业务的影响最大?

是的。这就是我们开始的地方。我认为这是一个很好的方法。此外,你提到的其中一件事是性能测试,尤其是在

像最终用户服务中。性能很重要,因为如果速度太慢,人们会认为它坏了,对吧?-是的。-但是性能很难,因为它,就像,它有点不稳定。你如何处理这个问题?-是的,我的意思是,它真的不稳定,而且它也高度依赖于你的工作负载。因此,如果你的工作负载不现实,你的性能测试实际上并没有为你测试任何东西。它会给你提供大致的方向,但不会提供很多有用的信息。

所以这是我另一篇博客文章的内容,即为什么负载测试如此困难?但我认为其核心在于,它必须与你的实际行为相匹配。而且你不知道,你不知道实际的工作负载将会是什么,直到你将其部署到生产环境中,你可以猜测人们将如何使用它。但是你无法获得真实的工作负载,直到它进入生产环境。所以总有一些不匹配。但是

最终,你必须尝试模拟,在理想情况下,模拟整个系统的端到端。用户将施加什么工作负载?因为如果你单独测试不同的服务,你就不会捕获系统不同组件之间的相互作用和非线性交互。你是否利用监控来尝试找出这个问题?

是的,所以我们喜欢查看我们的监控,以确保我们看到的实际行为与我们在性能测试中所做的工作相匹配。我认为,尤其是在电信公司,有一些非常有趣的关于这方面的研究,几年前我开始这个项目时读到过。

他们当时谈论的是如何根据监控自动生成合成工作负载。有趣。据我所知,除了电信公司之外,它还没有被付诸实践,因为它也很昂贵。所以电信公司,

回到风险讨论,除非你有很多钱押在上面,如果你的系统出现故障,那就不值得了。而如果你是一家电信公司,你是整个国家的紧急服务提供商,所以你最好保持正常运行,并投入资金来确保你这样做。

是的。此外,你提到的其中一件事是我们到目前为止暗示过的,但你在文章中直接提到的,是服务中断的成本或服务崩溃的成本与编写测试的成本之间的权衡。然后,我还非常注意维护测试的成本。

因为获得良好的覆盖率、良好的行为覆盖率以及大量的测试套件总是感觉很好。你感觉很舒服,但是如果你必须重构或事情发生变化,这个大型测试套件也是一个难题。你必须维护测试代码,就像维护测试一样。

你的其余代码。是的。我认为这就是你所说的,你针对端到端测试的目标是100%覆盖率,我认为你在重构方面会稍微好一些,因为如果你更改内部内容,你不会那么容易破坏测试,因为它们与它没有那么紧密地耦合。而如果你对单元测试有非常高的覆盖率,它与代码的实际结构紧密耦合,重构

会深入到更改测试的细节中。我还未在文章中提到另一个成本,那就是运行它们所需的时间。因此,随着你获得越来越多的任务,你将要么支付更多计算资源来更快地运行它们,要么等待更长时间。这会让人非常沮丧。

是的,即使是像Python库、PyTest插件或一些额外功能这样的小东西也很有趣。我认为我们中的一些人对CI有点懒惰,说,好吧,如果只有几分钟没关系。然后,但是我在……

在六个不同的Python版本和三个或四个不同的硬件平台上运行它,这会使它成倍增加。即使这些是并行运行的,

这需要大量的计算能力,而有时这并不重要,我见过我见过Python库,它们在大量的Python版本上进行了测试,它们并没有利用这一点,它们实际上并不需要这样做,它们可以将它固定在……

上下限,可能就可以了,这里有一些风险和好处,而且我的意思是,如果你真的不是特定于硬件的

我认为你并不需要一直都在多个硬件平台上运行。有很多纯Python库就是这样测试的,我认为不需要这样做。是的,你可以为不同的更改配置不同的配置。因此,随着更改的到来,你可能希望在最旧和最新的版本上测试它们,然后当你进行主要版本发布或定期在更多版本上进行测试时,你可以捕获这些罕见的更改,但你不必每次都这样做。

所以出于好奇,你的,如果你不想分享,你当然不必分享,但是你会认为一个简短的测试套件是什么,一个长的测试套件是什么?什么太长了?我认为如果我可以起身去冲咖啡,那就是

可能太长了。所以我认为5分钟太长了。但实际上对我来说,这还取决于我的注意力缺陷多动障碍药物是否有效?因为如果它没有生效,那么如果它在我从终端移开视线之前没有完成,我就会去别的地方,而且它需要多长时间并不重要。但如果它有效,我可以坐在那里等待几分钟。所以我认为个位数分钟是相当合理的。

两位数分钟就像这对你的团队和你的生产力有重大影响。你呢?是的。

好吧,好的。所以我没有选择,因为我每天都在处理很多硬件方面的事情。但是我们所做的是尝试将我们的测试模块化,以便可以处理特定的测试模块或测试目录或其他内容。而且那一点在几分钟内就可以完成。

所以,就像你说的那样,开发工作流程,如果你正在这个领域工作,你不应该等待10分钟。几分钟甚至有点长。所以我希望在我的日常工作中,它能在不到一分钟内完成。

但是一旦我认为,哦,这很好,我把它推送到合并,如果在CI中需要10到15分钟,我也可以接受,因为我可能已经在本地捕获了它。所以CI实际上只是在我意外破坏了一些东西的情况下为我提供支持,诸如此类的事情。所以我认为多层级很好,能够说,嘿,开发工作流程需要很快,但我们也需要彻底测试。

我认为任何认为即使一分钟也太长的人,因为你需要能够在每次按键时进行测试,这太疯狂了。我认为人们不应该担心这个问题。但是也许,我不知道,也许Rust开发者可以,因为Rust非常快。是的。我的意思是,在Rust中,你的测试可以非常快。你只需要等待编译时间才能运行它们。哦,对。好的。是的。

我忘了它是编译的。它是编译的,而且它也像,你运行过Go吗?是的。所以Go的编译速度非常非常快。你做过C++吗?是的。好的。所以这些都相当慢。然后Rust就像,好吧,我们正在等待。我们正在等待一段时间。它并不快。他们正在努力使其更快,但这绝对是Rust的一个痛点,就像

解释型语言如Python的优势之一是,是的,我可以运行它,它就在那里。是的。Python的一大优点是它无论如何都是编译的,但没有人意识到这一点,因为我们只是没有看到它。是的。我的意思是,这导致了一个真正哲学性的问题,那就是,编译是什么意思?因为对我来说,如果你有字节码,就像,对我来说,我认为是你所经历的动作造成了有意义的差异。它就像,

我直接运行脚本还是我有一个单独的编译步骤?-是的,你是否在Go中有一个编译步骤?-这取决于你。你当然可以不使用它直接运行脚本,但你也可以让它输出一个二进制文件,然后你可以单独运行它。

我想,好吧,我唯一运行的是构建的东西。所以我运行Hugo,它是用Go构建的,但我没有,我没有实际编译Hugo。我只是运行它。所以,是的。是的。所以,编译方面,你可以使用go run。我想是的。我已经很久没有直接运行它了,它会编译源代码然后运行它。或者你可以进行单独的编译,获得可分发的二进制文件并将其发送给某人,然后他们可以运行它。嗯。

但是所有这些快速的东西中令人愉快的一点是它正在帮助Python。至少,尤其是Rust正在帮助使Python更快,这很不错。我没有机会在Python边界上使用Rust,但这真的很酷。而且让我很高兴的是,我们可以更安全地做事情,并且在世界上少用一些C。是的。

是的,是的,好吧,所以我希望我能够在未来某个时候同意你的观点,我的一半工作是C++,所以,我不想完全抛弃C++,我的哀悼,我在以前的工作中受到了C++的创伤,对不起,是的,它很痛苦,你正在谈论编译时间,

呃,编译时间现在已经到了我们相当快的地步。我的意思是,相对而言,嗯,我们至少可以用分钟来计算。所以随便吧。是的。无论如何,嗯,Nicole,和你谈论测试真是太好了。嗯,我们将链接到,至少是,嗯,让我们看看“好东西太多”。你还提到了,提到了为什么,呃,为什么负载测试如此困难?嗯,嗯,我们完全会链接到它。然后还有你的,呃,

你的Rust教程,而且我迫不及待地想开始学习它。是的。我认为我们还可以添加古德哈特定律的链接,我们围绕代码覆盖率是一个糟糕的衡量指标进行了讨论,但这明确说明了原因。

是的。我之所以与100%代码覆盖率开玩笑的原因之一是我主要利用它来找出要删除哪些代码。我最喜欢的提高覆盖率的方法是删除无法访问的代码。所以无论如何。是的。

是的,我喜欢这样。但是人们会感到害怕。就像当我删除代码时,人们会说,但是我们需要它。就像证明你向我证明我们需要它。我会把它放回去。另外,我希望它在版本控制中。所以如果你需要它,它仍然在那里。是的,就是这样。无法忍受看到被注释掉的代码。我们以后可能需要这个。

如果我们需要,我们以后会得到它。不要注释掉代码。我的意思是,在短时间内是可以的,但这很糟糕。所以,好的。非常感谢你,Nicole。和你交谈很愉快。是的,你也是。非常感谢你邀请我。这很有趣。

感谢您的收听,并感谢所有通过购买课程支持本节目的听众,包括Hello PyTest(学习PyTest的最快方法)和《The Complete PyTest Course》(如果你想真正成为PyTest专家)。两者都可以在courses.pythontest.com上找到,你也可以在那里加入Python测试社区。现在就到这里。现在就去测试一些东西吧。