【问题标题】:unit test via output sanity checks通过输出完整性检查进行单元测试
【发布时间】:2012-02-11 03:26:18
【问题描述】:

我经常看到将固定输入输入程序的测试,通常通过差异检查生成的输出与固定(预期)输出。如果差异被接受,则认为代码通过了测试。

问题

1) 这是一个可接受的单元测试吗?

2) 通常单元测试输入是从文件系统中读入的并且很大 xml 文件(也许它们代表一个非常大的系统)。是否应该进行单元测试 触摸文件系统?或者单元测试会即时创建一个小输入 并将其提供给要测试的代码?

3) 如何将现有代码重构为可单元测试?

【问题讨论】:

    标签: unit-testing testing


    【解决方案1】:

    输出差异

    如果您的要求是产生具有一定准确度的输出,那么这样的测试绝对没问题。是您做出最终决定 - “这个输出是否足够好?”

    与文件系统对话

    您不希望您的测试与文件系统进行通信,因为依赖某些文件存在于某处,以便您的测试能够正常工作(例如,从配置文件中读取值)。测试输入 resources 有点不同 - 您通常可以将它们嵌入到您的测试(或至少是测试项目)中,将它们视为代码库的一部分,除此之外,它们通常应该被加载 在测试执行之前。例如,当测试相当大的 XML 时,将它们存储为单独的文件是合理的,而不是代码文件中的字符串(有时可以这样做)。

    重点是 - 您希望保持您的测试独立且可重复。如果您可以通过在运行时加载文件来实现这一点 - 它可能没问题。然而,将它们作为代码库/资源的一部分仍然比标准系统文件放在某处要好。

    重构

    这个问题相当宽泛,但是为了让你找到正确的方向——你想介绍更多solid 设计、decouple 对象和单独的职责。更好的设计将使测试更容易,最重要的是 - 可能。就像我说的,这是一个广泛而复杂的主题,entire books 致力于此。

    【讨论】:

      【解决方案2】:

      1) 这是一个可接受的单元测试吗?

      根据定义,这不是单元测试。单元测试关注尽可能少的代码。您的测试仍然可以是有用的测试、回归测试、自我记录测试、TDD 测试等。它只是不是单元测试,尽管它可能同样有用。

      2) 单元测试是否应该涉及文件系统?

      通常不会,除非您需要对与文件系统明确相关的内容进行单元测试。一个原因是,如果您有数百个单元测试,最好让它们在几秒钟而不是几分钟内运行。

      3) 如何将现有代码重构为可单元测试?

      一个更好的问题是为什么您希望代码是可单元测试的?如果您正在尝试学习 TDD,最好从一个新项目开始。如果您有错误,请尝试为错误编写测试。如果设计让您放慢了速度,那么您可以随着时间的推移重构可测试性。

      【讨论】:

        【解决方案3】:

        只解决第三个问题。这是非常困难的。您确实需要在编写代码的同时或之前编写测试。尝试在现有代码库上进行测试是一场噩梦,而丢弃代码并重新开始通常更有效率。

        【讨论】:

          【解决方案4】:
          1. 这是一个可接受的单元测试。

          2. 正在读取的文件应该是测试项目的一部分,因此从存储库中签出项目的任何人都将在相同的相对位置拥有相同的文件。

          3. 进行黑盒测试是一个很好的开始,您可以重构现有代码并使用当前测试来验证它是否仍然有效(取决于测试的质量)。这是一篇关于可测试性重构的简短博客:http://www.beletsky.net/2011/02/refactoring-to-testability.html

          【讨论】:

            【解决方案5】:

            差异测试可以作为单元测试接受,尤其是当您使用单元测试之间共享的测试数据时。

            如果您不知道 SUT 中有多少项目,您可以使用以下方法:

            int itemsBeforeTest = SUT.Items.Count;
            
            SUT.AddItem();
            
            Assert.AreEqual(itemsBeforeTest + 1, SUT.Items.Count);
            

            如果一个单元测试需要大量数据,以至于需要从一个大的 XML 文件中读取,那么它就不是真正的单元测试。单元测试应该完全隔离地测试一个类并模拟所有依赖项。

            使用Builder pattern 之类的模式也有助于为您的单元测试创​​建测试数据。将测试数据放在单独的文件中的最大问题是很难理解测试的确切作用。如果您在单元测试的安排部分中创建测试数据,那么您会立即清楚什么对您的测试很重要。

            例如,假设您有以下排列代码来测试发票价格是否正确:

            Address billingAddress = new Address("Stationsweg 9F",
                "Groningen", "Nederland", "9726AE"); shippingAddress = new Address("Aweg 1",
                "Groningen", "Nederland", "9726AB");
            Customer customer = new Customer(99, "Piet", "Klaassens",
                                    30,
                                    billingAddress,
                                    shippingAddress);
            Product product = new Product(88, "Tafel", 19.99);
            Invoice invoice = new Invoice(customer);
            

            使用Builder时可以改为如下

            Invoice invoice = Builder<Invoice>.CreateNew()
                                .With(i => i.Product = Builder<Product>.CreateNew()
                                    .With(p => p.Price = 19.99)
                                    .Build())
                                .Build();
            

            使用 Builder 时,更容易看出什么是重要的,并且您的代码也更易于维护。

            重构代码以提高可测试性是一个广泛的话题。归结为思考“我将如何测试这段代码?”在你写代码的时候。

            举个例子:

            public class AlarmClock
            {
                public AlarmClock()
                {
                    SatelliteSyncService = new SatelliteSyncService();
                    HardwareClient = new HardwareClient();
                }
            }
            

            这很难测试。在测试闹钟时,您需要确保 SatteliteSyncService 和 HardwareClient 都正常工作。

            对构造函数的这种更改使测试变得更加容易:

            public AlarmClock(IHardwareClient hardwareClient, ISatelliteSyncService satelliteSyncService)
            {
                SatelliteSyncService = satelliteSyncService;
                HardwareClient = hardwareClient;
            }
            

            Dependency Injection 等技术有助于重构您的代码,使其更具可测试性。还要注意静态值,例如 DateTime.Now 或使用 Singleton,因为它们很难测试。

            关于编写可测试代码的非常好的介绍可以在here 找到。

            【讨论】:

              【解决方案6】:

              您不应要求重构代码才能执行单元测试。单元测试,顾名思义,就是为系统测试一个代码单元。最好的单元测试是小型的、快速执行的,并且只运行被测试代码的一小部分(例如类)。

              只有小而紧凑的单元测试只运行一部分代码的原因是单元测试的目的是发现该代码单元中的错误。如果单元测试需要很长时间来执行并测试很多东西,那么识别代码中的错误就会变得更加困难。

              关于访问文件系统,我认为没有问题。一些单元测试可能需要在执行测试之前构建一个数据库,要检查的输出在代码中编写检查会很困难或很耗时。

              单元测试的文件应该像其他代码一样对待 - 置于版本控制之下。如果您偏执,您可以在单元测试中实施检查,例如对其执行 MD5 并检查硬编码值,以便将来重新运行测试可以验证测试数据没有意外更改。

              只是我的拙见。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2017-03-10
                相关资源
                最近更新 更多