java代码的可测性_帮助,我的代码不可测! 我需要修复设计吗?

java代码的可测性

我们的代码往往是不可测的,因为没有简单的方法来“感1”的好办法,结果因为代码依赖于外部数据/功能,而不使其能够更换或在测试期间修改这些(它缺少一个缝2 ,即可以在不修改代码本身的情况下更改代码行为的地方。 在这种情况下,最好的办法是修复设计以使代码可测试,而不是尝试编写脆弱而缓慢的集成测试。 让我们看一个这样的代码的示例以及如何修复它。

意大利面设计示例

以下代码是一种类似REST的服务,该服务从Amazon的Simple Storage Service(S3)获取文件列表,并将它们显示为指向文件内容的链接列表:

public class S3FilesResource {
 
     AmazonS3Client amazonS3Client;
 
      ...
 
     @Path('files')
     public String listS3Files() {
         StringBuilder html = new StringBuilder('<html><body>');
         List<S3ObjectSummary> files = this.amazonS3Client.listObjects('myBucket').getObjectSummaries();
         for (S3ObjectSummary file : files) {
             String filePath = file.getKey();
             if (!filePath.endsWith('')) {  exclude directories
                 html.append('<a href='content?fileName=').append(filePath).append(''>').append(filePath)
                     .append('<br>');
             }
         }
         return html.append('<body><html>').toString();
     }
 
     @Path('content')
     public String getContent(@QueryParam('fileName') String fileName) {
         throw new UnsupportedOperationException('Not implemented yet');
     }
 
 }


为什么代码难以测试?

  1. 没有接缝可以使我们绕过对S3的外部依赖,因此我们不能影响将什么数据传递给该方法,也不能轻松地使用不同的值对其进行测试。 此外,我们依靠网络连接和S3服务中的正确状态来运行代码。
  2. 很难感觉到方法的结果,因为它将数据与它们的表示混合在一起。 直接访问数据以验证目录是否被排除并显示期望的文件名会容易得多。 此外,与HTML表示相比,更改核心逻辑的可能性要小得多,但是即使逻辑不变,更改表示也会破坏我们的测试。

我们可以做些什么来改善它?

我们首先按原样测试代码,以确保我们的重构不会破坏任何东西(测试将是脆弱且丑陋的,但这只是暂时的),重构它以打破外部依赖关系并拆分数据和表示,最后重新编写测试。

我们首先编写一个简单的测试:

public class S3FilesResourceTest {
 
     @Test
     public void listFilesButNotDirectoriesAsHtml() throws Exception {
         S3FilesResource resource = new S3FilesResource(* pass AWS credentials ... *);
         String html = resource.listS3Files();
         assertThat(html)
             .contains('<a href='content?fileName=dirfile1.txt'>dirfile1.txt')
             .contains('<a href='content?fileName=diranother.txt'>diranother.txt')
             .doesNotContain('dir');  directories should be excluded
         assertThat(html.split(quote(''))).hasSize(2 + 1);  two links only
     }
 
 }


重构设计

这是重构的设计,其中我通过引入Facade / Adapter将代码与S3分离,并拆分了数据处理和渲染:

public interface S3Facade {
     List<S3File> listObjects(String bucketName);
 }
public class S3FacadeImpl implements S3Facade {
 
     AmazonS3Client amazonS3Client;
 
     @Override
     public List<S3File> listObjects(String bucketName) {
         List<S3File> result = new ArrayList<S3File>();
         List<S3ObjectSummary> files = this.amazonS3Client.listObjects(bucketName).getObjectSummaries();
         for (S3ObjectSummary file : files) {
             result.add(new S3File(file.getKey(), file.getKey()));  later we can use st. else for the display name
         }
         return result;
     }
 
 }
public class S3File {
     public final String displayName;
     public final String path;
 
     public S3File(String displayName, String path) {
         this.displayName = displayName;
         this.path = path;
     }
 }
public class S3FilesResource {
 
     S3Facade amazonS3Client = new S3FacadeImpl();
 
      ...
 
     @Path('files')
     public String listS3Files() {
         StringBuilder html = new StringBuilder('<html><body>');
         List<S3File> files = fetchS3Files();
         for (S3File file : files) {
             html.append('<a href='content?fileName=').append(file.path).append(''>').append(file.displayName)
                     .append('<br>');
         }
         return html.append('<body><html>').toString();
     }
 
     List<S3File> fetchS3Files() {
         List<S3File> files = this.amazonS3Client.listObjects('myBucket');
         List<S3File> result = new ArrayList<S3File>(files.size());
         for (S3File file : files) {
             if (!file.path.endsWith('')) {
                 result.add(file);
             }
         }
         return result;
     }
 
     @Path('content')
     public String getContent(@QueryParam('fileName') String fileName) {
         throw new UnsupportedOperationException('Not implemented yet');
     }
 
 }

在实践中,我将考虑使用Jersey的内置转换功能(带有用于HTML的自定义MessageBodyWriter ),并从listS3Files返回List<S3File>

这是现在测试的样子:

public class S3FilesResourceTest {
 
     private static class FakeS3Facade implements S3Facade {
         List<S3File> fileList;
 
         public List<S3File> listObjects(String bucketName) {
             return fileList;
         }
     }
 
     private S3FilesResource resource;
     private FakeS3Facade fakeS3;
 
     @Before
     public void setUp() throws Exception {
         fakeS3 = new FakeS3Facade();
         resource = new S3FilesResource();
         resource.amazonS3Client = fakeS3;
     }
 
     @Test
     public void excludeDirectories() throws Exception {
         S3File s3File = new S3File('file', 'file.xx');
         fakeS3.fileList = asList(new S3File('dir', 'mydir'), s3File);
         assertThat(resource.fetchS3Files())
             .hasSize(1)
             .contains(s3File);
     }
 
     ** Simplest possible test of listS3Files *
     @Test
     public void renderToHtml() throws Exception {
         fakeS3.fileList = asList(new S3File('file', 'file.xx'));
         assertThat(resource.listS3Files())
             .contains('file.xx');
     }
 }

接下来,我将为REST服务实现集成测试,但仍使用FakeS3Facade来验证该服务是否正常运行,并且可以在预期的URL上访问该文件,以及指向文件内容的链接也正常运行。 我还将为真正的S3客户端编写一个集成测试(通过S3FilesResource,但不在服务器上运行),该集成测试仅按需执行,以验证我们的S3凭据正确并且可以访问S3。 (我不想定期执行它,因为外部服务缓慢而脆弱。)

免责声明:上面的服务不是正确使用REST的一个很好的例子,为了简洁起见,我采用了一些简写,它们并不代表良好的代码。

祝您编程愉快,别忘了分享!

参考: 帮助,我的代码不可测! 我需要修复设计吗?The Holy Java博客上来自我们的JCG合作伙伴 Jakub Holy。


翻译自: https://www.javacodegeeks.com/2012/09/help-my-code-isnt-testable-do-i-need-to.html

java代码的可测性

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值