技巧一:处理实例化
在我想办法让我的Android App通过Unit Test和Integration Test的过程中,我积累了一些小的经验与技巧,可以成功的处理比较麻烦的代码。展示开始:
问题代码
我想测试这段代码
- public void readDeepLink(String path) {
- new DeepLinkReader().readDeepLink(path);
- }
我只是想确保readDeepLink()方法执行了,但是我不想真的实例化DeepLinkReader因为这会引起网络操作。所以怎么修改呢?
解决方法
将实例化DeepLinkReader的代码封装到另外一个方法中,这样就可以用一个mock覆盖这个方法,这就可以避免在测试时实例化DeepLinkReader而可以使用Mockito来确认mock方法的执行。
解决代码
- //在MainPresenter.java中
- public void readDeepLink(String path) {
- getDeepLinkReader().readDeepLink(path);
- }
- DeepLinkReader getDeepLinkReader() {
- return new DeepLinkReader(currentData, events);
- }
- //在MainPresenterTest.java中
- @Mock private DeepLinkReader deepLinkReader;
- @Test
- public void shouldReadDeepLink() throws Exception {
- MainPresenter mainPresenter = new MainPresenter() {
- DeepLinkReader getDeepLinkReader() {
- return deepLinkReader;
- }
- };
- mainPresenter.readDeepLink("washingtonpost.com");
- verify(deepLinkReader).readDeepLink("washingtonpost.com");
- }
技巧二:第三方类库
当你在为Anddroid App编写单元测试时,可能经常要处理第三方类库的API。在下面的代码中我用Picasso加载图片。Picasso的类库非常简洁,所以很好操作,但 当你用其他的第三方类库时可能就不是这样了。所以我们怎么简化和第三方类库的交互而同时编写可维护性强的代码呢?
- Picasso.with(contextLocal)
- .load(book.getImageUrl())
- .resize(80,108)
- .centerInside()
- .into(viewHolder.imageView)
我们可以将对第三方类库(这里以 Picasso 为例)的调用封装到另一个类中,使我们可以使用API并通过Assertion进行测试。
原始代码
- //MyClass.class
- public void loadImageFromUrl(String imageUrl) {
- presenter.setCurrentPreviewImageUrl(imageUrl);
- if (showThumbImage) {
- Picasso.with(context)
- .load(imageUrl)
- .into(thumbImageView);
- }
- }
在这个 loadImageFromUrl 方法中,我们希望确保我们的 Presenter 被调用了,而且当 showThumbImage 是 true 的时候加载图片。因为我们想在单元测试中验证这一点,所以我们不想真正加载这张图片。
为了达到目标我们新建两个类:ImageLoader 和 ImageLoaderImpl,第一个是用于暴露API的接口,第二个是实现了第一个接口的类,用于封装调用第三方API的操作。
修改代码
- // ImageLoader interface
- public interface ImageLoader {
- void loadImage(String imageUrl, ImageView imageView);
- }
- // ImageLoaderImpl class
- public class ImageLoaderImpl implements ImageLoader {
- // fields omitted
- @Override
- public void loadImage(String imageUrl, ImageView imageView) {
- Picasso.with(context).load(imageUrl).into(imageView);
- }
- }
- //loadImageFromUrl method
- public void loadImageFromUrl(String imageUrl) {
- presenter.setCurrentPreviewImageUrl(imageUrl);
- if (showThumbImage) {
- imageLoader.loadImage(imageUrl, thumbImageView);
- }
- }
Unit Test
- //MyClass
- @Mock private ImageLoader imageLoader;
- @Test
- public void shouldLoadImageWhenShowThumbIsTrue() throws Exception {
- myClass.setShowThumbImage(true);
- myClass.loadImageFromUrl("http://www.myimageurl.png");
- verify(imageLoader).loadImage(anyString(), any(ImageView.class));
- }
- @Test
- public void shouldNotLoadImageWhenShowThumbIsFalse() throws Exception {
- myClass.setShowThumbImage(false);
- myClass.loadImageFromUrl("http://www.myimageurl.png");
- verifyZeroInteractions(imageLoader);
- }
在这两个单元测试中我们可以成功地测试了 Image Loader 的相关操作而无需关心第三方API。而且如果将来要使用其他的图片加载类库,也无需改变测试代码。
技巧三:提高可读性
可读性是高质量测试代码的重要指标之一,而我们有可能会因为在测试中添加了无用的信息而降低可读性,来看个实例。
例子
我们现在要测试我们用于创建 Video Object 的URL与调用 VideoPlaybackService 中的 playCurrentVideo 方法的返回值适配。我们编写了一个helper方法,可以传入用于实例化 Video Object 的一些参数。但是大多数情况下我们不需要设置所有的参数,比如 duration 和 displayDate 对于这个方法的测试就不起到任何作用。也就是说,我们在测试中添加了额外的且无用的信息,使测试代码可读性降低。
- @Test
- public void shouldReceiveCurrentUrlWhenVideoAvailable() {
- String url = "http://www.my_video.mp4";
- String displayDate = "Jan. 08, 2016";
- double duration = 0.30;
- playbackService.setCurrentVideo(createVideo(url, displayDate, duration));
- String actualUrl = playbackService.playCurrentVideo();
- assertEquals("the urls are not the same", url, actualUrl);
- }
- private Video createVideo(String url, String displayDate, double duration) {
- Video video = new Video();
- video.setUrl(url);
- video.setDisplayDate(displayDate);
- video.setDuration(duration);
- return video;
- }
解决方案
我们可以通过使用建造者(Builder)模式优化 Video 类,从而使额外的属性不再是必需的。下面是视频的URL的setter方法的未经优化的代码。
- public void setUrl(String url) {
- this.url = url;
- }
我们可以升级这个方法,使它回传Video对象(this)而不是返回空。让我们看一看这可以怎样增强测试代码的可读性和理解性。
使用建造者模式
- public Video setUrl(String url) {
- this.url = url;
- return this;
- }
- @Test
- public void shouldReceiveCurrentUrlWhenVideoAvailable() {
- String url = "http://www.my_video.mp4";
- playbackService.setCurrentVideo(new Video().setUrl(url));
- String actualUrl = playbackService.playCurrentVideo();
- assertEquals("the urls are not the same", url, actualUrl);
- }
这样代码就具有更强的可读性和可维护性了。