单元测试编写_编写详尽的单元测试

单元测试编写

As software developers we all know how important it is to unit test the code that we write. Some of us write tests that verify the happy paths only (bad) and others look for high code coverage numbers. But how many of you are actually writing thorough tests? Let’s take a look at what this means.

作为软件开发人员,我们都知道对所编写的代码进行单元测试的重要性。 我们中的一些人编写的测试仅验证(不正确的)快乐路径,而另一些人则寻找较高的代码覆盖率数字。 但是实际上你们当中有多少人在编写全面的测试? 让我们看看这意味着什么。

Thorough - Test normal & failure conditions, invalid inputs, and boundary conditions.

彻底-测试正常和故障条件,无效输入和边界条件。

Repeatable - Tests must always give you the same results every time so avoid depending on things that might change such as an external server.

可重复-测试每次都必须始终为您提供相同的结果,因此请避免依赖可能发生变化的事物(例如外部服务器)。

Focused - Tests should exercise one specific aspect of your code at a time. A failure in a unit test should lead you to an actual bug in your code.

重点突出-测试应该一次练习代码的一个特定方面。 单元测试失败会导致您在代码中遇到实际错误。

Verifies Behavior - Tests should test behavior without making assumptions on implementations. Avoid over mocking with Mockito.

验证行为-测试应在不对实现进行假设的情况下测试行为。 避免与Mockito过度嘲笑。

Fast - Tests should be fast since 70% of your tests should consist of unit tests.

快速-测试应该快速,因为70%的测试应该包含单元测试。

Concise - Tests should be easy to understand and be a clear blueprint of what the code under test is doing.

简洁-测试应该易于理解,并且是被测代码正在做什么的清晰蓝图。

测试1:不可重复 (Test 1: Not Repeatable)

Image for post

Above we see a test that is testing SomeClass#getCurrentMonthString. Way too often I see this mistake made during code reviews and it violates the repeatability characteristic. The reason is the default locale, timezone, and system time are being used. This means 1 month from now this test will return March instead of February which will instantly begin failing.

在上方,我们看到一个测试SomeClass#getCurrentMonthString的测试。 我经常看到此错误是在代码审查期间犯的,它违反了可重复性特征。 原因是使用了默认语言环境,时区和系统时间。 这意味着从现在开始的1个月内该测试将返回3月而不是2月,而2月将立即开始失败。

Let’s see how this same test looks with these issues fixed.

让我们看看修复了这些问题后的测试结果。

Image for post

Here we can see we preset the timezone, locale, and timestamp so that we obtain consistent results.

在这里,我们可以看到我们预设了时区,语言环境和时间戳,以便获得一致的结果。

测试2:不快速+可重复+彻底 (Test 2: Not Fast + Repeatable + Thorough)

Image for post

This test just hurts me to look at. First off it’s doing network io in an actual unit test. Unit tests should be hermetic and mock network io so you mock responses and error conditions (401/503/200) to make sure your code handles every possibility.

这个测试让我难以接受。 首先,它是在实际的单元测试中进行网络io。 单元测试应该是封闭的并且模拟网络io,因此您可以模拟响应和错误条件(401/503/200),以确保您的代码能够处理所有可能性。

It includes a sleep statement with some magical expectation that every time it’s run it will only take 10s to finish the network call. Please don’t use sleep statements EVER.

它包含一个睡眠声明,并带有一些神奇的期望,即每次运行它只需10秒钟即可完成网络通话。 请永远不要使用睡眠语句。

It’s also testing a method that performs some work asynchronously. Tests should always test against synchronous code.

它还正在测试一种异步执行某些工作的方法。 测试应始终针对同步代码进行测试。

测试3:不彻底 (Test 3: Not Thorough)

Let’s assume we have a calculator class that provides addition, subtraction, multiplication, and division.

假设我们有一个提供加,减,乘和除的计算器类。

Here we have two test functions for addition and subtraction. Each test has verified the core function is working as intended.

这里我们有两个加减法测试功能。 每个测试均已验证核心功能是否按预期工作。

Image for post

While this will tick the checkbox for increased code coverage it’s clearly violating the thorough characteristic. You can’t just test the happy paths. You have to be extremely thorough and think of every possible way you can break your code and write tests accordingly. You should be adding two positive numbers, adding a positive & a negative number, a negative and a negative number, etc. You get the picture. Do not just test a single condition and move on. That’s not going to keep bugs from showing up in production code.

尽管这将选中复选框以增加代码覆盖率,但它显然违反了全面的功能。 您不能仅仅测试幸福的道路。 您必须非常透彻,并考虑各种可能的方式来破坏代码并相应地编写测试。 您应该添加两个正数,添加一个正数和一个负数,一个负数和一个负数,等等。 不要只是测试一个条件并继续前进。 这不会阻止错误出现在生产代码中。

测试4:不验证行为 (Test 4: Not Verifying Behavior)

Image for post

Here we have a custom EditText view that overrides the setText() function and prepends “vm-” to anything passed in. Let me explain why this is a bad test and why you should avoid Mockito.verify(). The common thinking here is that since EditText comes with Android then it has it’s own unit tests already so we don’t need to verify that it’s working.

在这里,我们有一个自定义的EditText视图,该视图将覆盖setText()函数,并在传入的任何内容前加上“ vm-”。让我解释一下为什么这是一个不好的测试,以及为什么应该避免使用Mockito.verify()。 这里的普遍想法是,由于EditText是Android附带的,因此它已经具有自己的单元测试,因此我们不需要验证其是否正常工作。

Let’s say 6 months down the road someone wants to introduce a new parent class named CleanupEditText and one of its jobs is to strip out dashes from any input string. In this example with only a few lines of code it’s very easy to see where I’m going with this but imagine this class has 1000+ lines of code. Now the dev changes the parent class of MyEditText to this new class and runs all the tests to verify he hasn’t broken anything. The test that was written for MyEditText passes because all it is doing is verifying that the parent instance of setText() was called.

假设六个月后有人要引入一个名为CleanupEditText的新父类,其工作之一就是从任何输入字符串中去除破折号。 在这个只有几行代码的示例中,很容易看到我要去哪里,但是可以想象这个类有1000多行代码。 现在,开发人员将MyEditText的父类更改为这个新类,并运行所有测试以确认他没有破坏任何东西。 为MyEditText编写的测试通过了,因为它所做的只是验证是否调用了setText()的父实例。

Image for post

Now we’ve broken the expected behavior and have no way of knowing because the above test is not verifying behavior. It is only verifying a method is being called.

现在,我们已经破坏了预期的行为,并且无法知道,因为上述测试并未验证行为。 它只是在验证一种方法正在被调用。

Here is that same test fixed so it verifies behavior.

这是固定的同一测试,因此可以验证行为。

Image for post

How easy was that? Yet it’s so instinctive to reach for Mockito.verify() and abuse it without thinking of the possible consequences.

那有多容易? 但是,到达Mockito.verify()并滥用它而不考虑可能的后果是非常本能的。

测试5:不专心 (Test 5: Not Focused)

Now let’s assume we have a class named Calculator again with basic mathematical operations supported.

现在,我们假设我们又有了一个名为Calculator的类,它支持基本的数学运算。

Now someone writes a single test function called testOperations as shown below.

现在,有人编写了一个称为testOperations的测试函数,如下所示。

Image for post

We should ensure we are identifying units of code and testing those separately otherwise you end up with an integration test as shown above. Remember, this article is about improving your unit tests and unit tests test business logic in isolation.

我们应该确保确定代码单元并分别测试它们,否则您将进行如上所示的集成测试。 请记住,本文是关于改进单元测试和单元测试独立测试业务逻辑的。

Image for post

Here we have the same test broken up into 3 focused unit tests that are each testing only a single aspect of your code. The test function names have also been modified to more clearly explain what is being tested, with what inputs, and expected result.

在这里,我们将同一测试分为3个重点单元测试,每个单元测试仅测试代码的一个方面。 测试功能名称也已修改,以更清楚地说明正在测试的内容,输入的内容以及预期的结果。

测试6:不简洁+彻底 (Test 6: Not Concise + Thorough)

Here we have a class named AnnualPayCalculator that computes an annual pay given hourly pay and total weekly hours worked.

在这里,我们有一个名为AnnualPayCalculator的类,该类根据给定的小时工资和每周工作总小时数计算年工资。

Below you’ll see a test function that verifies the expected result is returned.

在下面,您将看到一个测试函数,用于验证是否返回了预期的结果。

Image for post

The main concern here is this test is not concise. It’s way too verbose and requires you to really read the test to understand what’s being tested.

这里主要关注的是这个测试不够简洁。 它太冗长,需要您真正阅读测试以了解所测试的内容。

Here is the same test written more concisely.

这是同样简洁的测试。

Image for post

测试命名 (Test Naming)

This brings us to our last characteristic of a good unit test that was not called out previously. Naming.

这将我们带到了以前没有提到过的良好单元测试的最后一个特征。 命名。

This is sure to be controversial because on one hand you are used to writing shorter function names in production code.

这肯定会引起争议,因为一方面您习惯于在生产代码中编写较短的函数名称。

But, for test functions, there’s a crucial factor that changes the equation: you never write calls to test functions. A developer types out a test name exactly once — in the function signature. Given this, brevity still matters, but it matters less than in production code.

但是,对于测试函数,有一个改变方程式的关键因素:您永远不会编写对测试函数的调用 。 开发人员只需在函数签名中输入一次测试名称即可。 鉴于此,简洁仍然很重要,但是它比生产代码中的重要性要小。

Whenever a test breaks, the test name is the first thing you see, so it should communicate as much as possible.

每当测试失败时,您都会首先看到测试名称,因此它应该尽可能多地传达信息。

Format:

格式:

when_expected
when_with_expected

Bad examples:

错误的例子:

testOnActivityCreated
testGetHashedAttachmentFilenames

Good examples:

很好的例子:

viewCreated_listViewExists
getHashedAttachmentFilenames_withNullList_emptyStringReturned

摘要 (Summary)

  • Don’t just write unit tests. Include integration and workflow where needed. Don’t waste time on these if they aren’t absolutely necessary to cover something that isn’t already being tested.

    不要只是编写单元测试。 在需要时包括集成和工作流程。 如果不是绝对不需要涵盖尚未经过测试的内容,请不要在这些内容上浪费时间。
  • Make sure your tests follow the characteristics of a good unit test

    确保您的测试遵循良好的单元测试的特征
  • Name your tests properly

    正确命名测试
  • A failure in a test should immediately tell you exactly what is broken and under what condition

    测试失败应立即告诉您什么地方坏了,在什么情况下
  • Always strive to test functionality/behavior. Verifying a method is called only should be rarely used

    始终努力测试功能/行为。 验证方法仅被调用应该很少使用

翻译自: https://medium.com/swlh/writing-thorough-unit-tests-591292d5a571

单元测试编写

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值