使用 C# 和 RhinoMocks 进行测试驱动开发的最佳实践

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/124210/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-03 14:40:01  来源:igfitidea点击:

Best Practices of Test Driven Development Using C# and RhinoMocks

提问by Kevin Albrecht

In order to help my team write testable code, I came up with this simple list of best practices for making our C# code base more testable. (Some of the points refer to limitations of Rhino Mocks, a mocking framework for C#, but the rules may apply more generally as well.) Does anyone have any best practices that they follow?

为了帮助我的团队编写可测试的代码,我想出了这个简单的最佳实践列表,以提高我们的 C# 代码库的可测试性。(其中一些要点是指 Rhino Mocks(C# 的模拟框架)的局限性,但这些规则也可能更普遍地适用。)有没有人有任何他们遵循的最佳实践?

To maximize the testability of code, follow these rules:

为了最大限度地提高代码的可测试性,请遵循以下规则:

  1. Write the test first, then the code.Reason: This ensures that you write testable code and that every line of code gets tests written for it.

  2. Design classes using dependency injection.Reason: You cannot mock or test what cannot be seen.

  3. Separate UI code from its behavior using Model-View-Controller or Model-View-Presenter.Reason: Allows the business logic to be tested while the parts that can't be tested (the UI) is minimized.

  4. Do not write static methods or classes.Reason: Static methods are difficult or impossible to isolate and Rhino Mocks is unable to mock them.

  5. Program off interfaces, not classes.Reason: Using interfaces clarifies the relationships between objects. An interface should define a service that an object needs from its environment. Also, interfaces can be easily mocked using Rhino Mocks and other mocking frameworks.

  6. Isolate external dependencies.Reason: Unresolved external dependencies cannot be tested.

  7. Mark as virtual the methods you intend to mock.Reason: Rhino Mocks is unable to mock non-virtual methods.

  1. 先写测试,再写代码。原因:这可确保您编写可测试的代码,并且每一行代码都为它编写了测试。

  2. 使用依赖注入设计类。原因:你不能模拟或测试看不到的东西。

  3. 使用 Model-View-Controller 或 Model-View-Presenter 将 UI 代码与其行为分开。原因:允许测试业务逻辑,同时最小化无法测试的部分(UI)。

  4. 不要编写静态方法或类。原因:静态方法很难或不可能隔离,Rhino Mocks 无法模拟它们。

  5. 编程接口,而不是类。原因:使用接口澄清对象之间的关系。接口应该定义一个对象需要从其环境中获得的服务。此外,可以使用 Rhino Mocks 和其他模拟框架轻松模拟接口。

  6. 隔离外部依赖。原因:无法测试未解决的外部依赖项。

  7. 将您打算模拟的方法标记为虚拟。原因:Rhino Mocks 无法模拟非虚拟方法。

采纳答案by aridlehoover

Definitely a good list. Here are a few thoughts on it:

绝对是一个不错的清单。这里有一些想法:

Write the test first, then the code.

先写测试,再写代码。

I agree, at a high level. But, I'd be more specific: "Write a test first, then write just enoughcode to pass the test, and repeat." Otherwise, I'd be afraid that my unit tests would look more like integration or acceptance tests.

我同意,在高层次上。但是,我会更具体:“首先编写一个测试,然后编写足够的代码以通过测试,然后重复。” 否则,我会担心我的单元测试看起来更像是集成或验收测试。

Design classes using dependency injection.

使用依赖注入设计类。

Agreed. When an object creates its own dependencies, you have no control over them. Inversion of Control / Dependency Injection gives you that control, allowing you to isolate the object under test with mocks/stubs/etc. This is how you test objects in isolation.

同意。当对象创建自己的依赖项时,您无法控制它们。控制反转/依赖注入为您提供这种控制,允许您使用模拟/存根/等隔离被测对象。这就是孤立测试对象的方式。

Separate UI code from its behavior using Model-View-Controller or Model-View-Presenter.

使用 Model-View-Controller 或 Model-View-Presenter 将 UI 代码与其行为分开。

Agreed. Note that even the presenter/controller can be tested using DI/IoC, by handing it a stubbed/mocked view and model. Check out Presenter FirstTDD for more on that.

同意。请注意,即使是演示者/控制器也可以使用 DI/IoC 进行测试,方法是将其传递给存根/模拟视图和模型。查看Presenter FirstTDD 了解更多信息。

Do not write static methods or classes.

不要编写静态方法或类。

Not sure I agree with this one. It is possible to unit test a static method/class without using mocks. So, perhaps this is one of those Rhino Mock specific rules you mentioned.

不确定我是否同意这一点。可以在不使用模拟的情况下对静态方法/类进行单元测试。所以,也许这是您提到的 Rhino Mock 特定规则之一。

Program off interfaces, not classes.

编程接口,而不是类。

I agree, but for a slightly different reason. Interfaces provide a great deal of flexibility to the software developer - beyond just support for various mock object frameworks. For example, it is not possible to support DI properly without interfaces.

我同意,但原因略有不同。接口为软件开发人员提供了极大的灵活性——不仅仅是对各种模拟对象框架的支持。例如,没有接口就不可能正确支持 DI。

Isolate external dependencies.

隔离外部依赖。

Agreed. Hide external dependencies behind your own facade or adapter (as appropriate) with an interface. This will allow you to isolate your software from the external dependency, be it a web service, a queue, a database or something else. This is especiallyimportant when your team doesn't control the dependency (a.k.a. external).

同意。使用接口隐藏您自己的外观或适配器(视情况而定)背后的外部依赖项。这将允许您将您的软件与外部依赖项隔离开来,无论是 Web 服务、队列、数据库还是其他东西。当您的团队不控制依赖项(也称为外部)时,这一点尤其重要。

Mark as virtual the methods you intend to mock.

将您打算模拟的方法标记为虚拟。

That's a limitation of Rhino Mocks. In an environment that prefers hand coded stubs over a mock object framework, that wouldn't be necessary.

这是 Rhino Mocks 的一个限制。在更喜欢手动编码存根而不是模拟对象框架的环境中,这不是必需的。

And, a couple of new points to consider:

并且,需要考虑几个新点:

Use creational design patterns.This will assist with DI, but it also allows you to isolate that code and test it independently of other logic.

使用创建性设计模式。这将有助于 DI,但它也允许您隔离该代码并独立于其他逻辑对其进行测试。

Write tests using Bill Wake's Arrange/Act/Assert technique.This technique makes it very clear what configuration is necessary, what is actually being tested, and what is expected.

使用Bill Wake 的 Arrange/Act/Assert 技术编写测试。这种技术非常清楚哪些配置是必要的,哪些是实际测试的,哪些是预期的。

Don't be afraid to roll your own mocks/stubs.Often, you'll find that using mock object frameworks makes your tests incredibly hard to read. By rolling your own, you'll have complete control over your mocks/stubs, and you'll be able to keep your tests readable. (Refer back to previous point.)

不要害怕推出自己的模拟/存根。通常,您会发现使用模拟对象框架会使您的测试难以阅读。通过滚动你自己的,你将完全控制你的模拟/存根,你将能够保持你的测试可读。(请返回上一点。)

Avoid the temptation to refactor duplication out of your unit tests into abstract base classes, or setup/teardown methods.Doing so hides configuration/clean-up code from the developer trying to grok the unit test. In this case, the clarity of each individual test is more important than refactoring out duplication.

避免将单元测试中的重复重构为抽象基类或设置/拆卸方法。这样做会向试图理解单元测试的开发人员隐藏配置/清理代码。在这种情况下,每个单独测试的清晰度比重构重复更重要。

Implement Continuous Integration.Check-in your code on every "green bar." Build your software and run your full suite of unit tests on every check-in. (Sure, this isn't a coding practice, per se; but it is an incredible tool for keeping your software clean and fully integrated.)

实施持续集成。在每个“绿色条”上签入您的代码。构建您的软件并在每次签入时运行全套单元测试。(当然,这本身并不是一种编码实践;但它是一种令人难以置信的工具,可以让您的软件保持干净并完全集成。)

回答by George Mauer

Good list. One of the things that you might want to establish - and I can't give you much advice since I'm just starting to think about it myself - is when a class should be in a different library, namespace, nested namespaces. You might even want to figure out a list of libraries and namespaces beforehand and mandate that the team has to meet and decide to merge two/add a new one.

好清单。您可能想要建立的一件事 - 我不能给你太多建议,因为我自己才刚刚开始思考 - 是一个类何时应该位于不同的库、命名空间、嵌套命名空间中。您甚至可能想事先找出库和命名空间的列表,并要求团队必须开会并决定合并两个/添加一个新的。

Oh, just thought of something that I do that you might want to also. I generally have a unit tests library with a test fixture per class policy where each test goes into a corresponding namespace. I also tend to have another library of tests (integration tests?) which is in a more BDD style. This allows me to write tests to spec out what the method should do as well as what the application should do overall.

哦,刚刚想到我做的一些你可能也想做的事情。我通常有一个单元测试库,每个类策略都有一个测试装置,其中每个测试都进入相应的命名空间。我还倾向于拥有另一个更BDD 风格的测试库(集成测试?)。这允许我编写测试来指定方法应该做什么以及应用程序应该总体做什么。

回答by Hamish Smith

Know the difference between fakes, mocks and stubsand when to use each.

了解fakes、mocks 和 stubs之间的区别以及何时使用它们。

Avoid over specifying interactions using mocks. This makes tests brittle.

避免使用模拟过度指定交互。这使得测试变得脆弱

回答by zadam

If you are working with .Net 3.5, you may want to look into the Moqmocking library - it uses expression trees and lambdas to remove non-intuitive record-reply idiom of most other mocking libraries.

如果您正在使用 .Net 3.5,您可能需要查看Moq模拟库 - 它使用表达式树和 lambda 来删除大多数其他模拟库的非直观记录-回复习惯用法。

Check out this quickstartto see how much more intuitive your test cases become, here is a simple example:

查看此快速入门以了解您的测试用例变得更加直观,这是一个简单的示例:

// ShouldExpectMethodCallWithVariable
int value = 5;
var mock = new Mock<IFoo>();

mock.Expect(x => x.Duplicate(value)).Returns(() => value * 2);

Assert.AreEqual(value * 2, mock.Object.Duplicate(value));

回答by George Mauer

Here's a another one that I thought of that I like to do.

这是我想到的另一个我喜欢做的。

If you plan to run tests from the unit test Gui as opposed to from TestDriven.Net or NAnt then I've found it easier to set the unit testing project type to console application rather than library. This allows you to run tests manually and step through them in debug mode (which the aforementioned TestDriven.Net can actually do for you).

如果您计划从单元测试 Gui 运行测试而不是从 TestDriven.Net 或 NAnt 运行测试,那么我发现将单元测试项目类型设置为控制台应用程序而不是库更容易。这允许您手动运行测试并在调试模式下逐步执行它们(前面提到的 TestDriven.Net 实际上可以为您完成)。

Also, I always like to have a Playground project open for testing bits of code and ideas I'm unfamiliar with. This should not be checked into source control. Even better, it should be in a separate source control repository on the developer's machine only.

此外,我总是喜欢打开一个 Playground 项目来测试我不熟悉的代码和想法。这不应该检查到源代码管理中。更好的是,它应该仅位于开发人员机器上的单独源代码控制存储库中。

回答by George Mauer

This is a very helpful post!

这是一个非常有用的帖子!

I would add that it is always important to understand the Context and System Under Test (SUT). Following TDD principals to the letter is much easier when you're writing new code in an environment where existing code follows the same principals. But when you're writing new code in a non TDD legacy environment you find that your TDD efforts can quickly balloon far beyond your estimates and expectations.

我要补充一点,了解上下文和被测系统 (SUT) 始终很重要。当您在现有代码遵循相同原则的环境中编写新代码时,遵循 TDD 原则要容易得多。但是当您在非 TDD 遗留环境中编写新代码时,您会发现您的 TDD 工作可能会迅速膨胀,远远超出您的估计和期望。

For some of you, who live in an entirely academic world, timelines and delivery may not be important, but in an environment where software is money, making effective use of your TDD effort is critical.

对于生活在完全学术世界中的一些人来说,时间表和交付可能并不重要,但在软件就是金钱的环境中,有效利用您的 TDD 工作至关重要。

TDD is highly subject to the Law of Diminishing Marginal Return. In short, your efforts towards TDD are increasingly valuable until you hit a point of maximum return, after which, subsequent time invested into TDD has less and less value.

TDD 高度服从边际收益递减规律。简而言之,您在 TDD 上的努力会越来越有价值,直到您达到最大回报点,之后投入到 TDD 上的时间价值就会越来越小。

I tend to believe that TDD's primary value is in boundary (blackbox) as well as in occasional whitebox testing of mission-critical areas of the system.

我倾向于认为 TDD 的主要价值在于边界(黑盒)以及系统关键任务区域的偶尔白盒测试。

回答by Steve Freeman

The real reason for programming against interfaces is not to make life easier for Rhino, but to clarify the relationships between objects in the code. An interface should define a service that an object needs from its environment. A class provides a particular implementation of that service. Read Rebecca Wirfs-Brock's "Object Design" book on Roles, Responsibilities, and Collaborators.

针对接口编程的真正原因不是为了让 Rhino 的生活更轻松,而是为了澄清代码中对象之间的关系。接口应该定义一个对象需要从其环境中获得的服务。类提供该服务的特定实现。阅读 Rebecca Wirfs-Brock 关于角色、职责和合作者的“对象设计”一书。