如何将单元测试引入大型遗留 (C/C++) 代码库?

发布于 2024-07-16 18:03:49 字数 1392 浏览 4 评论 0原文

我们有一个用 C 编写的大型多平台应用程序。(使用少量但不断增长的 C++)它已经发展了多年,具有您在大型 C/C++ 应用程序中所期望的许多功能:

  • #ifdef hell
  • 大文件使得隔离可测试代码
  • 变得困难 函数过于复杂而难以轻松测试

由于此代码是针对嵌入式设备的,因此在实际目标上运行它会产生大量开销。 因此,我们希望在本地系统上以快速周期进行更多开发和测试。 但我们希望避免“复制/粘贴到系统上的 .c 文件中,修复错误,复制/粘贴回来”的经典策略。 如果开发人员愿意不厌其烦地这样做,我们希望稍后能够重新创建相同的测试,并以自动化的方式运行。

这是我们的问题:为了将代码重构得更加模块化,我们需要它更加可测试。 但为了引入自动化单元测试,我们需要它更加模块化。

一个问题是,由于我们的文件太大,我们可能在文件内有一个函数,该函数调用同一文件中的函数,我们需要将其存根以进行良好的单元测试。 随着我们的代码变得更加模块化,这似乎不再是一个问题,但这还有很长的路要走。

我们想做的一件事是用注释标记“已知可测试”的源代码。 然后我们可以编写一个脚本扫描源文件以获取可测试代码,将其编译在单独的文件中,并将其与单元测试链接。 当我们修复缺陷并添加更多功能时,我们可以慢慢引入单元测试。

然而,有人担心维护这个方案(以及所有必需的存根函数)会变得太麻烦,开发人员将停止维护单元测试。 因此,另一种方法是使用自动为所有代码生成存根的工具,并将文件与其链接。 (我们发现唯一可以做到这一点的工具是昂贵的商业产品)但是这种方法似乎要求在我们开始之前我们所有的代码都更加模块化,因为只有外部调用可以被掐掉了。

就我个人而言,我宁愿让开发人员考虑他们的外部依赖关系并明智地编写他们自己的存根。 但是,要消除一个极其庞大的 10,000 行文件的所有依赖项,这可能会让人难以承受。 可能很难说服开发人员他们需要维护所有外部依赖项的存根,但这是正确的方法吗? (我听到的另一个论点是子系统的维护者应该维护其子系统的存根。但我想知道“强迫”开发人员编写自己的存根是否会带来更好的单元测试?)#

ifdefs ,当然,为问题添加另一个整体维度。

我们已经研究了几个基于 C/C++ 的单元测试框架,有很多看起来不错的选项。 但我们还没有找到任何东西可以缓解从“没有单元测试的毛茸茸的代码”到“可单元测试的代码”的过渡。

因此,我向经历过这种情况的其他人提出以下问题:

  • 什么是好的起点? 我们正朝着正确的方向前进,还是遗漏了一些明显的东西?
  • 哪些工具可能有助于过渡? (最好是免费/开源,因为我们现在的预算大致为“零”)

请注意,我们的构建环境是基于 Linux/UNIX 的,因此我们不能使用任何仅限 Windows 的工具。

We have a large, multi-platform application written in C. (with a small but growing amount of C++) It has evolved over the years with many features you would expect in a large C/C++ application:

  • #ifdef hell
  • Large files that make it hard to isolate testable code
  • Functions that are too complex to be easily testable

Since this code is targeted for embedded devices, it's a lot of overhead to run it on the actual target. So we would like to do more of our development and testing in quick cycles, on a local system. But we would like to avoid the classic strategy of "copy/paste into a .c file on your system, fix bugs, copy/paste back". If developers are going to to go the trouble to do that, we'd like to be able to recreate the same tests later, and run in an automated fashion.

Here's our problem: in order to refactor the code to be more modular, we need it to be more testable. But in order to introduce automated unit tests, we need it to be more modular.

One problem is that since our files are so large, we might have a function inside a file that calls a function in the same file that we need to stub out to make a good unit test. It seems like this would be less of a problem as our code gets more modular, but that is a long way off.

One thing we thought about doing was tagging "known to be testable" source code with comments. Then we could write a script scan source files for testable code, compile it in a separate file, and link it with the unit tests. We could slowly introduce the unit tests as we fix defects and add more functionality.

However, there is concern that maintaining this scheme (along with all the required stub functions) will become too much of a hassle, and developers will stop maintaining the unit tests. So another approach is to use a tool that automatically generates stubs for all the code, and link the file with that. (the only tool we have found that will do this is an expensive commercial product) But this approach seems to require that all our code be more modular before we can even begin, since only the external calls can be stubbed out.

Personally, I would rather have developers think about their external dependencies and intelligently write their own stubs. But this could be overwhelming to stub out all the dependencies for a horribly overgrown, 10,000 line file. It might be difficult to convince developers that they need to maintain stubs for all their external dependencies, but is that the right way to do it? (One other argument I've heard is that the maintainer of a subsystem should maintain the stubs for their subsystem. But I wonder if "forcing" developers to write their own stubs would lead to better unit testing?)

The #ifdefs, of course, add another whole dimension to the problem.

We have looked at several C/C++ based unit test frameworks, and there are a lot of options that look fine. But we have not found anything to ease the transition from "hairball of code with no unit tests" to "unit-testable code".

So here are my questions to anyone else who has been through this:

  • What is a good starting point? Are we going in the right direction, or are we missing something obvious?
  • What tools might be useful to help with the transition? (preferably free/open source, since our budget right now is roughly "zero")

Note, our build environment is Linux/UNIX based, so we can't use any Windows-only tools.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(13

骷髅 2024-07-23 18:03:50

我曾参与过具有完全单元测试代码库的 Green field 项目和经过多年发展并有许多不同开发人员参与的大型 C++ 应用程序。

老实说,我不会费心去尝试让遗留代码库达到单元测试和测试优先开发可以增加很多价值的状态。

一旦遗留代码库达到一定的规模和复杂性,使其达到单元测试覆盖率可以为您带来很多好处的程度,就变成了相当于完全重写的任务。

主要问题是,一旦开始重构可测试性,就会开始引入错误。 只有当您获得高测试覆盖率时,您才能期望所有这些新错误都能被发现并修复。

这意味着您要么非常缓慢且小心地进行,直到几年后您才能获得经过良好单元测试的代码库的好处。 (可能自从合并等发生以来就从未出现过。)与此同时,您可能会引入一些对软件的最终用户没有明显价值的新错误。

或者,您速度很快,但代码库不稳定,直到您的所有代码都达到了高测试覆盖率。 (所以你最终会得到 2 个分支,一个在生产中,一个用于单元测试版本。)

当然,对于某些项目来说,这一切都是规模问题,重写可能只需要几周的时间,而且肯定是值得的。

I have worked on both Green field project with fully unit tested code bases and large C++ applications that have grown over many years and with many different developers on them.

Honestly, I would not bother an attempt to get a legacy code base to the state where units tests and test first development can add a lot of value.

Once a legacy code base gets to a certain size and complexity getting it to the point where unit test coverage provides you with a lot of benefits becomes a task equivalent to a full rewrite.

The main problem is that as soon as you start refactoring for testability you will begin introducing bugs. And only once you get high test coverage can you expect all those new bugs to be found and fixed.

That means that you either go very slowly and carefully and you do not get the benefits of a well unit tested code base until years from now. (probably never since mergers etc happen.) In the mean time you are probably introducing some new bugs with no apparent value to the end user of the software.

Or you go fast but have an unstable code base until all you have reached high test coverage of all your code. (So you end up with 2 branches, one in production, one for the unit-tested version.)

Of cause this all a matter of scale for some projects a rewrite might just take a few weeks and can certainly be worth it.

冰火雁神 2024-07-23 18:03:50

一种需要考虑的方法是首先放置一个系统范围的模拟框架,您可以使用它来开发集成测试。 从集成测试开始可能看起来违反直觉,但在您描述的环境中进行真正的单元测试的问题是相当可怕的。 可能不仅仅是模拟软件中的整个运行时......

这种方法会简单地绕过您列出的问题 - 尽管它会给您带来许多不同的问题。 但在实践中,我发现使用强大的集成测试框架,您可以开发在单元级别执行功能的测试,尽管没有单元隔离。

PS:考虑编写一个命令驱动的模拟框架,可以基于 Python 或 Tcl 构建。 这将使您可以轻松地编写测试脚本......

One approach to consider is to first put a system-wide simulation framework in place that you could use to develop integration tests. Starting with integration tests might seem counter-intuitive, but the problems in doing true unit-testing in the environment you describe are quite formidable. Probably moreso than just simulating the entire run-time in software...

This approach would simply bypass your listed issues -- although it would give you many different ones. In practice though, I've found that with a robust integration testing framework you can develop tests that exercise functionality at the unit level, although without unit isolation.

PS: Consider writing a command-driven simulation framework, maybe built on Python or Tcl. This will let you script tests quite easily...

嘴硬脾气大 2024-07-23 18:03:50

天哪,

我首先看一下任何明显的点,例如在头文件中使用 dec 。

然后开始查看代码是如何布局的。 符合逻辑吗? 也许开始将大文件分解成较小的文件。

也许可以买一本 Jon Lakos 的优秀著作《大规模 C++ 软件设计》(已清理的 Amazon链接)以获得一些关于如何布局的想法。

一旦您开始对代码库本身更有信心,即代码布局与文件布局一样,并清除了一些不好的气味,例如在头文件中使用 dec,那么您就可以开始挑选一些您可以使用的功能用于开始编写单元测试。

选择一个好的平台,我喜欢 CUnit 和 CPPUnit,然后从那里开始。

但这将是一段漫长而缓慢的旅程。

HTH

欢呼,

G'day,

I'd start by having a look at any obvious points, e.g. using dec's in header files for one.

Then start looking at how the code has been laid out. Is it logical? Maybe start breaking large files down into smaller ones.

Maybe grab a copy of Jon Lakos's excellent book "Large-Scale C++ Software Design" (sanitised Amazon link) to get some ideas on how it should be laid out.

Once you start getting a bit more faith in the code base itself, i.e. code layout as in file layout, and have cleared up some of the bad smells, e.g. using dec's in header files, then you can start picking out some functionality that you can use to start writing your unit tests.

Pick a good platform, I like CUnit and CPPUnit, and go from there.

It's going to be a long, slow journey though.

HTH

cheers,

手心的海 2024-07-23 18:03:50

首先使其更加模块化要容易得多。 你无法真正对具有大量依赖项的东西进行单元测试。 何时重构是一个棘手的计算。 您确实必须权衡成本、风险与收益。 这段代码会被广泛重用吗? 或者这段代码真的不会改变吗? 如果您打算继续使用它,那么您可能需要重构。

听起来你想重构。 您需要从分解最简单的实用程序开始,并在它们的基础上进行构建。 你的 C 模块可以完成无数的事情。 例如,也许其中有一些代码总是以某种方式格式化字符串。 也许这可以成为一个独立的实用模块。 您已经有了新的字符串格式化模块,使代码更具可读性。 已经是一个进步了。 您声称自己处于第 22 条军规的情况。 你真的不是。 只需移动一些东西,就可以使代码更具可读性和可维护性。

现在您可以为这个分解的模块创建一个单元测试。 您可以通过几种方式做到这一点。 您可以制作一个单独的应用程序,其中仅包含您的代码并在 PC 上的主例程中运行一堆案例,或者定义一个名为“UnitTest”的静态函数,该函数将执行所有测试用例并在通过时返回“1”。 这可以在目标上运行。

也许您不能 100% 采用这种方法,但它是一个开始,它可能会让您看到其他可以轻松分解为可测试实用程序的东西。

Its much easier to make it more modular first. You can't really unittest something with a whole lot of dependencies. When to refactor is a tricky calculation. You really have to weigh the costs and risks vs the benefits. Is this code something that will be reused extensively? Or is this code really not going to change. If you plan to continue to get use out of it, then you probably want to refactor.

Sounds like though, you want to refactor. You need to start by breaking out the simplest utilities and build on them. You have your C module that does a gazillion things. Maybe, for example, there's some code in there that is always formatting strings a certain way. Maybe this can be brought out to be a stand-alone utility module. You've got your new string formatting module, you've made the code more readable. Its already an improvement. You are asserting that you are in a catch 22 situation. You really aren't. Just by moving things around, you've made the code more readable and maintainable.

Now you can create a unittest for this broken out module. You can do that a couple of ways. You can make a separate app that just includes your code and runs a bunch of cases in a main routine on your PC or maybe define a static function called "UnitTest" that will execute all the test cases and return "1" if they pass. This could be run on the target.

Maybe you can't go 100% with this approach, but its a start, and it may make you see other things that can be easily broken out into testable utilities.

記憶穿過時間隧道 2024-07-23 18:03:50

使测试变得简单。

我首先将“自动运行”落实到位。 如果您希望开发人员(包括您自己)编写测试,请使其易于运行并查看结果。

编写三行测试,针对最新版本运行它并查看结果应该只需单击一下即可,而不是将开发人员带到咖啡机上。

这意味着您需要最新的版本,您可能需要更改人们处理代码的策略等。我知道这样的过程可以是带有嵌入式设备的 PITA,我对此无法提供任何建议。 但我知道,如果运行测试很困难,那么就没有人会编写它们。

测试可以测试的内容

我知道我在这里违背了常见的单元测试理念,但这就是我所做的:为易于测试的事物编写测试。 我不关心模拟,我不重构以使其可测试,如果涉及 UI,我没有单元测试。 但我的图书馆日常活动中越来越多地有这样一个。

我对简单的测试往往会发现的结果感到非常惊讶。 摘取唾手可得的果实绝不是无用的。

从另一个角度来看:如果它不是一个成功的产品,你就不会打算维持这个巨大的毛团混乱。 您当前的质量控制并非完全失败,需要更换。 相反,在易于执行的地方使用单元测试。

(不过,您需要完成它。不要陷入构建过程中“修复所有内容”的困境。)

教导如何改进您的代码库

任何具有该历史的代码库都迫切需要改进,这是肯定的。 不过,你永远不会重构所有的内容。

看看具有相同功能的两段代码,大多数人都会同意哪一段在给定方面(性能、可读性、可维护性、可测试性……)下“更好”。 困难的部分是三个:

  • 如何平衡不同方面
  • 如何同意这段代码足够好
  • 如何将糟糕的代码变成足够好的代码而不破坏任何东西。

第一点可能是最难的,既是一个社会问题,也是一个工程问题。 但其他的点都是可以学习的。 我不知道有任何采用这种方法的正式课程,但也许你可以在内部组织一些东西:从两个人一起写作到“研讨会”,你可以在其中编写一段令人讨厌的代码并讨论如何改进它。


Make using tests easy.

I'd start with putting the "runs automatically" into place. If you want developers (including yourself) to write tests, make it easy to run them, and see the results.

Writing a test of three lines, running it against the latest build and seeing the results should be only one click away, and not send the developer to the coffe machine.

This means you need a latest build, you may need to change policies how people work on code etc. I know that such a process can be a PITA with embedded devices, and I can't give any advice with that. But I know that if running the tests is hard, noone will write them.

Test what can be tested

I know I run against common Unit Test philosophy here, but that's what I do: Write tests for the things that are easy to test. I don't bother with mocking, I don't refactor to make it testable, and if there is UI involved i don't have a unit test. But more and more of my library routines have one.

I am quite amazed what simple tests tend to find. Picking the low hanging fruits is by no means useless.

Looking at it in another way: You wouldn't plan to maintain that giant hairball mess if it wasn't a successful product. You current quality control isn't a total failure that needs to be replaced. Rather, use unit tests where they are easy to do.

(You need to get it done, though. Don't get trapped into "fixing everything" around your build process.)

Teach how to improve your code base

Any code base with that history screams for improvements, that's for sure. You will never refactor all of it, though.

Looking at two pieces of code with the same functionality, most people can agree which one is "better" under a given aspect (performance, readability, maintainability, testability, ...). The hard parts are three:

  • how to balance the different aspects
  • how to agree that this piece of code is good enough
  • how to turn bad code into good enough code without breaking anything.

The first point is probably the hardest, and as much a social as an engineering question. But the other points can be learned. I don't know any formal courses that take this approach, but maybe you can organize something in-house: anything from two guys worting together to "workshops" where you take a nasty piece of code and discuss how to improve it.


站稳脚跟 2024-07-23 18:03:50

这一切都有一个哲学方面。

您真的想要经过测试的、功能齐全的、整洁的代码吗? 这是你的目标吗? 您从中得到任何好处吗?

是的,乍一看这听起来很愚蠢。 但老实说,除非您是系统的实际所有者,而不仅仅是员工,否则错误就意味着更多的工作,更多的工作意味着更多的钱。 当你在处理毛球时,你会感到非常快乐。

我只是在这里猜测,但是,您在这场巨大的斗争中所承担的风险可能远高于您通过整理代码而获得的可能回报。 如果你缺乏社交技巧来解决这个问题,你只会被视为麻烦制造者。 我见过这些人,我也曾是其中之一。 当然,如果你能成功完成这件事,那就太酷了。 我会印象深刻。

但是,如果您觉得现在被迫花费额外的时间来保持杂乱的系统正常运行,那么您真的认为一旦代码变得整洁和漂亮,这种情况就会改变吗? 不......一旦代码变得漂亮和整洁,人们将获得所有这些空闲时间,在第一个可用的截止日期之前再次完全销毁它。

最终,是管理创造了良好的工作场所,而不是代码。

There is a philosophical aspect to it all.

Do you really want tested, fully functional, tidy code? Is it YOUR objective? Do YOU get any benefit at all from it?.

yes, at first this sounds totally stupid. But honestly, unless you are the actual owner of the system, and not just an employee, then bugs just means more work, more work means more money. You can be totally happy while working on a hairball.

I am just guessing here, but, the risk you are taking by taking on this huge fight is probably much higher than the possible pay back you get by getting the code tidy. If you lack the social skills to pull this through, you will just be seen as a troublemaker. I've seen these guys, and I've been one too. But of course, it's pretty cool if you do pull this through. I would be impressed.

But, if you feel you are bullied into spending extra hours now to keep an untidy system working, do you really think that that will change once the code gets tidy and nice?. No.. once the code gets nice and tidy, people will get all this free time to totally destroy it again at the first available deadline.

in the end it's the management that creates the workplace nice, not the code.

风吹过旳痕迹 2024-07-23 18:03:50

我认为,基本上你有两个不同的问题:

  1. 需要重构的大型代码库
  2. 与团队

合作 模块化、重构、插入单元测试等是一项艰巨的任务,我怀疑任何工具都可以接管这项工作的大部分。 这是一项罕见的技能。 有些程序员可以做得很好。 最讨厌它。

与团队一起完成这样的任务是很乏味的。 我强烈怀疑“强迫”开发者的做法是否有效。 Iains 的想法非常好,但我会考虑寻找一两个能够并且想要“清理”源代码的程序员:重构、模块化、引入单元测试等。让这些人完成工作,其他人引入新的错误,aehm 功能。 只有喜欢那种工作的人才能在这份工作上取得成功。

I think, basically you have two of separate Problems:

  1. Large Code base to refactor
  2. Work with a team

Modularization, refactoring, inserting Unit tests and alike is a difficult task, and i doubt that any tool could take over larger parts of that work. Its a rare skill. Some Programmers can do that very well. Most hate it.

Doing such a task with a team is tedious. I strongly doubt that '"forcing" developers' ever will work. Iains thoughts are very well, but I would consider finding one or two programmers who are able to and who want to "clean up" the sources: Refactor, Modualrize, introduce Unit Tests etc. Let these people do the job and the others introduce new bugs, aehm functions. Only people who like that kind of work will succeed with that job.

谢绝鈎搭 2024-07-23 18:03:50

不确定这是否真实,但我在这里有一些小建议。 据我了解,您提出了关于将单元测试增量非侵入性集成到大量遗留代码中的方法论问题,并且许多利益相关者都在保护他们的沼泽。

通常,第一步是独立于所有其他代码构建测试代码。 即使是长期遗留代码中的这一步也非常复杂。 我建议将您的测试代码构建为具有运行时链接的动态共享库。 这将允许您仅重构正在测试中的一小段代码,而不是整个 20K 文件。 因此,您可以开始逐个功能地覆盖功能,而无需触及/修复所有链接问题

Not sure is it actual or not, but I have small advice here. As I understand, you ask methodological question about incremental non-invasive integration of unit testing into huge legacy code with a lot of stakeholders protecting their swamp.

Usually, first step is to build your testing code independently from all other code. Even this step in long-live legacy code is very complex. I propose to build your testing code as a dynamic shared library with run-time linking. That will allow you to refactor only small piece of code which is undertesting and not whole 20K file. So, you can start covering function by function without touching/fixing all linking issues

最笨的告白 2024-07-23 18:03:49

我们还没有发现任何东西可以缓解“毛团”的过渡
没有单元测试的代码”到“可单元测试的代码”。

多么悲伤——没有奇迹般的解决方案——只是纠正多年积累的大量艰苦工作技术债务

没有简单的过渡。你有一个大的、复杂的、严重的问题。

你只能通过微小的步骤来解决它。每一个微小的步骤都涉及到 。

  1. 选择一个绝对必要的离散代码(不要在垃圾中啃咬边缘。)选择一个重要的组件,并且可以以某种方式从其余的功能中分离出来 是理想的,它可能是一个错综复杂的函数簇,也可能是一个完整的函数文件。从对可测试组件来说不太完美的东西开始是可以的。

  2. 弄清楚它应该做什么。 弄清楚它的界面应该是什么。 为此,您可能需要进行一些初始重构以使您的目标部分实际上是离散的。

  3. 编写一个“整体”集成测试,现在可以或多或少地测试您发现的离散代码片段。 在尝试更改任何重要内容之前,请先通过此测试。

  4. 将代码重构为整洁、可测试的单元,这比您当前的毛团更有意义。 您必须(暂时)与整体集成测试保持一定的向后兼容性。

  5. 为新单元编写单元测试。

  6. 一旦一切通过,停用旧的 API 并修复因更改而损坏的内容。 如有必要,返工原有的集成测试; 它测试旧的API,你想测试新的API。

迭代。

we have not found anything to ease the transition from "hairball of
code with no unit tests" to "unit-testable code".

How sad -- no miraculous solution -- just a lot of hard work correcting years of accumulated technical debt.

There is no easy transition. You have a large, complex, serious problem.

You can only solve it in tiny steps. Each tiny step involves the following.

  1. Pick a discrete piece of code that's absolutely essential. (Don't nibble around the edges at junk.) Pick a component that's important and -- somehow -- can be carved out of the rest. While a single function is ideal, it might be a tangled cluster of functions or maybe a whole file of functions. It's okay to start with something less than perfect for your testable components.

  2. Figure out what it's supposed to do. Figure out what it's interface is supposed to be. To do this, you may have to do some initial refactoring to make your target piece actually discrete.

  3. Write an "overall" integration test that -- for now -- tests your discrete piece of code more-or-less as it was found. Get this to pass before you try and change anything significant.

  4. Refactor the code into tidy, testable units that make better sense than your current hairball. You're going to have to maintain some backward compatibility (for now) with your overall integration test.

  5. Write unit tests for the new units.

  6. Once it all passes, decommission the old API and fix what will be broken by the change. If necessary, rework the original integration test; it tests the old API, you want to test the new API.

Iterate.

烂人 2024-07-23 18:03:49

Michael Feathers 就此撰写了圣经:有效地使用旧代码

Michael Feathers wrote the bible on this, Working Effectively with Legacy Code

孤君无依 2024-07-23 18:03:49

我对遗留代码和引入测试的一点经验是创建“特征化测试”。 您开始使用已知输入创建测试,然后获取输出。 这些测试对于您不知道它们真正做什么但知道它们正在工作的方法/类很有用。

然而,有时几乎不可能创建单元测试(甚至特征测试)。 在这种情况下,我通过验收测试来解决问题(在本例中为 Fitnesse)。

您创建测试一个功能所需的全部类并在fitnesse上检查它。 它与“表征测试”类似,但更高一级。

My little experience with legacy code and introducing testing would be to create the "Characterization tests". You start creating tests with known input and then get the output. These tests are useful for methods/classes that you don't know what they really do, but you know that they are working.

However, there are sometimes when it's nearly impossible to create unit tests (even characterization tests). On that case I attack the problem through acceptance tests (Fitnesse in this case).

You create the whole bunch of classes needed to test one feature and check it on fitnesse. It's similar to "characterization tests" but it's one level higher.

懷念過去 2024-07-23 18:03:49

正如乔治所说,《有效地处理遗留代码》是这类事情的圣经。

然而,团队中其他人接受的唯一方式是他们看到保持测试正常运行对他们个人的好处。

为了实现这一点,您需要一个尽可能易于使用的测试框架。 计划其他开发人员以您的测试为例来编写自己的测试。 如果他们没有单元测试经验,不要指望他们花时间学习框架,他们可能会认为编写单元测试会减慢他们的开发速度,因此不了解框架是跳过测试的借口。

花一些时间使用 Cruise Control、luntbuild、cdash 等进行持续集成。如果您的代码每晚自动编译并运行测试,那么开发人员将开始看到单元测试在质量检查之前捕获错误的好处。

值得鼓励的一件事是共享代码所有权。 如果开发人员更改了他们的代码并破坏了其他人的测试,他们不应期望该人修复他们的测试,他们应该调查测试不起作用的原因并自行修复。 根据我的经验,这是最难实现的事情之一。

大多数开发人员都会编写某种形式的单元测试,有时是一小段废弃的代码,他们不会签入或集成构建。 让将这些集成到构建中变得容易,开发人员将开始接受。

我的方法是添加新的测试,并且随着代码的修改,有时您无法在不解耦太多现有代码的情况下添加尽可能多或尽可能详细的测试,偏向实用。

我坚持进行单元测试的唯一地方是特定于平台的代码。 当 #ifdefs 被平台特定的更高级别的函数/类替换时,必须在所有平台上使用相同的测试来测试这些函数/类。 这节省了添加新平台的大量时间。

我们使用 boost::test 来构建我们的测试,简单的自注册函数使编写测试变得容易。

它们被包装在 CTest(CMake 的一部分)中,它一次运行一组单元测试可执行文件并生成一个简单的报告。

我们的夜间构建是通过 ant 和 luntbuild 实现自动化的(ant 粘合 c++、.net 和 java 构建)

很快我希望在构建中添加自动化部署和功能测试。

As George said Working Effectively with Legacy Code is the bible for this kind of thing.

However the only way others in your team will buy in is if they see the benefit to them personally of keeping the tests working.

To achieve this you require a test framework with which is as easy as possible to use. Plan for other developers you take your tests as examples to write their own. If they do not have unit testing experience, don't expect them to spend time learning a framework, they will probably see writing unit testings as slowing their development so not knowing the framework is an excuse to skip the tests.

Spend some time on continuous integration using cruise control, luntbuild, cdash etc. If your code is automatically compiled every night and tests run then developers will start to see the benefits if unit tests catch bugs before qa.

One thing to encourage is shared code ownership. If a developer changes their code and breaks someone else's test they should not expect that person to fix their test, they should investigate why the test is not working and fix it themselves. In my experience this is one of the hardest things to achieve.

Most developers write some form of unit test, some times a small piece of throw-away code they don't check in or integrate the build. Make integrating these into the build easy and developers will start to buy in.

My approach is to add tests for new and as code is modified, sometimes you cannot add as many or as detailed tests as you would like without decoupling too much existing code, err on the side of the practical.

The only place i insist on unit tests is on platform specific code. Where #ifdefs are replaces with platform specific higher level functions/classes, these must be tested on all platforms with the same tests. This saves loads of time adding new platforms.

We use boost::test to structure our test, the simple self registering functions make writing tests easy.

These are wrapped in CTest (part of CMake) this runs a group of unit tests executables at once and generates a simple report.

Our nightly build is automated with ant and luntbuild (ant glues c++, .net and java builds)

Soon I hope to add automated deployment and functional tests to the build.

寄意 2024-07-23 18:03:49

我们正在做这件事。 三年前,我加入了一个项目的开发团队,没有单元测试,几乎没有代码审查,并且构建过程相当临时。

代码库由一组COM组件(ATL/MFC)、跨平台C++ Oracle数据盒和一些Java组件组成,全部使用跨平台C++核心库。 有些代码已经有近十年的历史了。

第一步是添加一些单元测试。 不幸的是,该行为是非常数据驱动的,因此在生成单元测试框架(最初是 CppUnit,现在扩展到 JUnit 和 NUnit 的其他模块)方面进行了一些初步工作,该框架使用数据库中的测试数据。 大多数初始测试都是测试最外层的功能测试,而不是真正的单元测试。 您可能需要花费一些精力(您可能需要为此做好预算)来实施测试工具。

我发现如果您尽可能降低添加单元测试的成本,这会很有帮助。 测试框架使得在修复现有功能中的错误时添加测试相对容易,新代码可以进行适当的单元测试。 当您重构和实现新的代码区域时,您可以添加适当的单元测试来测试更小的代码区域。

去年,我们增加了与 CruiseControl 的持续集成,并自动化了我们的构建过程。 这增加了保持测试最新和通过的更多动力,这在早期是一个大问题。 因此,我建议您将定期(至少每晚)单元测试运行作为开发过程的一部分。

我们最近专注于改进我们的代码审查流程,但这种流程相当罕见且效率低下。 目的是使启动和执行代码审查的成本大大降低,以便鼓励开发人员更频繁地进行代码审查。 另外,作为我们流程改进的一部分,我正在努力争取时间在项目规划中以较低的级别进行代码审查和单元测试,以确保单个开发人员必须更多地考虑它们,而以前只有固定的比例花在他们身上的时间更容易在日程安排中迷失。

We are in the process of doing exactly this. Three years ago I joined the development team on a project with no unit tests, almost no code reviews, and a fairly ad-hoc build process.

The code base consists of a set of COM components (ATL/MFC), cross-platform C++ Oracle data cartridge and some Java components, all using a cross-platform C++ core library. Some of the code is nearly a decade old.

The first step was adding some unit tests. Unfortunately, the behaviour is very data-driven, so there was some initial effort in generating a unit test framework (initially CppUnit, now extended to other modules with JUnit and NUnit), which uses test data from a database. Most of the initial tests were functional tests which excercised the outermost layers and not really unit tests. You will probably have to expend some effort (which you may need to budget for) to implement a test harness.

I find it helps a lot if you make the cost of adding unit tests as low as possible. The test framework made it relatively easy to add tests when fixing bugs in existing functionality, new code can have proper unit tests. As you refactor and implementn new areas of code you can add proper unit tests which test much smaller areas of code.

In the last year we have added continuous integration with CruiseControl and automated our build process. This adds much more incentive to keep tests up-to-date and passing, which was a big problem in the early days. So I would recommend that you include regular (at least nightly) unit test runs as part of your development process.

We have recently focussed on improving our code review process, which was fairly infrequent and ineffective. The intent is to make it much cheaper to initiate and perform a code review so that developers are encouraged to do them more often. Also as part of our process improvement I am trying to get time for code reviews and unit tests included in project planning at a much lower level in a way that ensures individual developers have to think more about them, whereas previously there was just a fixed proportion of time devoted to them that was much easier to get lost in the schedule.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文