单元测试-无法测试返回对象的相等性,应测试什么?

huangapple go评论74阅读模式
英文:

Unit test-Cant test for equality of returned object, what to test for?

问题

Java 11,对TDD和单元测试都不太了解。
我有一个包含唯一方法(buy())的类。
它执行一些操作,比如在productRepo中搜索产品,准备收据。
最终它会返回一个对象(receipt)。

public Receipt buy(){
   // 查找产品
   // 创建收据
   return receipt; 
}

注意:我省略了参数,因为它们对问题不重要。

现在我必须为这个方法编写单元测试。我遇到了一些问题:
通常我会运行一个测试,并将期望的收据与调用返回的收据进行比较。
然而:

  1. Receipt类没有getter方法(只有一个构造函数)
  2. Receipt类没有重写equals方法,它不只有简单的属性(double,string等)
  3. 我不能在项目中引入任何新的依赖,我只有:mockito和junit。
  4. buy()方法必须绝对返回Receipt的实例。

所以我无法进行相等性测试。我该怎么办?
以下是我尝试过的:

  1. 我验证了从我的模拟对象(productRepository)中调用了一些方法。
  2. 我尝试扩展Receipt类并自己实现equals方法。然而,Object类的通用equals方法似乎在我的测试中被调用。

有什么想法或提示吗?
谢谢

英文:

Java 11, New to TDD and to unit testing in general.
I have a class that contains a unique method : buy()
It does a few thing like search for the product in the productRepo, prepare the receipt.
It finally returns an object (receipt).

public Receipt buy(){
   // find product
   // create the receipt
   return receipt; 
}

NOTE : I omitted the parameters because they are not important to the problem

Now I have to do the unit tests for that method. I have a few problems :
I would normally run a test and compare the expectedReceipt with the one obtained from the call.
However :

  1. the Receipt class has no getters (only a constructor)
  2. theReceipt class doesnt override the equals method and doesnt only have simple attributes (double, string ,etc)
  3. I cant include any new dependency to my project, I got : mockito and junit.
  4. The method buy() has to absolutely return an instance of Receipt.

So I have no way of testing for equality. What can I do ?
Heres what I tried :

  1. I verified that some method was called from my mock (productRepository).
  2. I tried extending that Receipt class and implement myself an equals method. However,
    the generic equals method from the Object class seems to be called in my tests.

Any ideas or hints ?
Thank you

答案1

得分: 0

这个问题突出了单元测试中一个重要(在我看来,经常被忽视的)方面。它们的目的不仅仅是断言软件(一个单元)的正确行为,还要作为可执行的文档形式,展示这个软件片段应该如何使用。

你在努力测试这个方法的事实突显出,一个“真实”的方法用户会在使用它时遇到困难 - 他们会调用这个方法,并得到一个实际上无法使用的对象。在现实世界的情况下,如果你面临这样的问题,很可能你不会费力地编写测试,而是回过头去修复 Receipt 类,使其更容易(可能?)使用。

话虽如此,由于这是一个学校作业,你不能改变方法/类的签名,在这里有几种可能的方法:

  1. 你忽略了 buy 方法的参数。这可能是一个设计不佳(返回一个没有 getter 的对象也是如此),但我会仔细检查是否有任何这些参数通过调用该方法进行了修改。如果是这样,你可以断言它们以所需的方式进行了修改。

  2. 正如你提到的,你可以模拟方法使用的外部服务(例如 productRepo),并验证它们是否以正确的参数被调用。

  3. 你可以使用反射来访问返回的 Receipt 对象的私有字段,并断言它们的值(有关详细信息,请参见 https://stackoverflow.com/q/1196192/2422776)。这种做法通常不被提倡,因为它违反了封装的原则,可能会产生非常脆弱的测试。然而,如果唯一可行的选项是一个糟糕的选项,那么也许它并没有那么糟糕。

  4. 负面测试 - 你没有分享关于如果使用无效输入(例如,一个不存在或缺货的产品)调用方法时它的行为如何。它会抛出异常吗?会返回 null 吗?这些行为是可以并且应该被测试的。

英文:

This question highlights an import (and IMHO, often overlooked) aspect of [unit] tests. Their purpose isn't to only assert the correct behavior of a piece of software (a unit), but to serve as an executable form of documentation, and to show how this piece of software is supposed to be used.

The fact that you're struggling to test this method highlights the fact that a "real" user of this method would struggle to use it - they would call this method and get back an object they can't really use. In a real world scenario, if you were faced with such a problem, chances are you wouldn't try to bend over backwards to write a test, but go back and fix the Receipt class to make it easier (possible?) to use.

Having said that, since this is a school assignment and you can't change the method/class' signature, there are a few possible approaches here:

  1. You omitted the arguments buy gets. It may be a poor design (as is returning an object with no getters, TBH), but I'd double check if any of these arguments are modified by calling the method. If so, you could assert that they are modified in the desired way.

  2. As you mentioned, you could mock the external services (e.g., the productRepo) the method uses, and verify they are called with the correct arguments.

  3. You could use reflection to access the private fields of the returned Receipt object, and assert their values (see, e.g., https://stackoverflow.com/q/1196192/2422776 for details). This practice is usually frowned upon as it breaks the principle of encapsulation, and may produce a very fragile test. Then again, if the only option that works is a bad option, maybe it isn't that bad.

  4. Negative testing - you didn't share any information about how the method behaves if you call it with invalid input (e.g., a product that doesn't exist or is out of stock). Does it throw an exception? Does it return null? These behaviors can and should be tested.

huangapple
  • 本文由 发表于 2020年9月13日 10:20:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/63866569.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定