2016-12-31 288 views
0

我正在测试一个android应用程序,并且正在使用我的大学为我提供的一个库,1-4级来自我的讲师供我们使用。Android单元测试:我如何测试?

我有一个类结构如下所示:

ClassOne

public ClassOne { 
    private ClassTwo clsTwo; 
    ... 
    public ClassOne(ClassTwo p1) 
    public ClassTwo getClsTwo(); 
} 

ClassTwo的结构为这样:

public ClassTwo { 
    private ClassThree clsThree; 
    ... 
    public ClassTwo() 
    public ClassThree getClsThree(); 
} 

ClassThree的结构为这样:

public ClassThree { 
    private HashMap<Bitmap> mBitmaps; 
    ... 
    private ClassFour clsFour; 
    ... 
    public ClassThree(ClassFour p1); 
    ... 
    public loadFile(String path, String name); 
    public loadFileFromAssetStore(String name); 
} 

CLA ssFour的结构为这样:

public ClassFour { 
    ... 
    public ClassFour(Context context); 
    ... 
} 

我测试是ClassFive,具体有方法的类强调了正在造成的问题:

public ClassFive { 
    private Bitmap myBitmap 
    ... 
    public ClassFive(...,...,...,ClassOne p,...){ 
     super(..., p, 
      p.getClsTwo().getClsThree().loadFileFromAssetStore("Default value")); 
     this.myBitmap = loadCorrectFile(...,p); 
    } 
    private Bitmap loadCorrectFile(..., ClassOne p){ 
     String strCorrectFileName; 
     switch(...){ 
      ... 
      // set value of strCorrectFileName 
      ... 
     } 
     this.myBitmap = p.getClsTwo().getClsThree().loadFileFromAssetStore(strCorrectFileName); 
    } 
} 

我的问题是我需要测试使用的构造方法ClassFive,但是当用NPE调用构造函数时,所有测试都会'翻倒'。

public class ClassFiveTest { 

@Mock 
private ClassOne mockClassOne = Mockito.Mock(ClassOne.class); 

@Test 
public void testConstructorGetName() throws Exception { 
    ClassFive instance = new ClassFive(..., mockClassOne); 
    ... 
    // Assertions here 
    ... 
} 

我的问题是,被返回一个空指针异常之前我的测试可以让我的断言。我需要使用mockito吗?因为我尝试过 - 也许我只是在这个实例中使用了错误。或者我需要使用仪器测试?当我尝试进行仪器测试时,发现无法访问ClassOne和ClassTwo?

回答

3

这很容易通过一些stubbing补救。

@Mock private ClassOne mockClassOne; // Don't call `mock`; let @Mock handle it. 
@Mock private ClassTwo mockClassTwo; 
@Mock private ClassThree mockClassThree; 

@Override public void setUp() { 
    MockitoAnnotations.initMocks(this); // Inits fields having @Mock, @Spy, and @Captor. 
    when(mockClassOne.getClsTwo()).thenReturn(mockClassTwo); 
    when(mockClassTwo.getClsThree()).thenReturn(mockClassThree); 

    // Now that you can get to mockClassThree, you can stub that too. 
    when(mockClassThree.loadFileFromAssetStore("Default value")).thenReturn(...); 
    when(mockClassThree.loadFileFromAssetStore("Your expected filename")).thenReturn(...); 
} 

总之,是的Mockito设计制作容易的更换类的实例,所以你可以用你的类被测检查您互动:在这里,你正在创建假的(“双考”)ClassOne的实现,ClassTwo和ClassThree,用于测试ClassFive。 (您也可以选择使用真实的实现或手动编写的假实现,如果其中任何一个对于您的特定情况比Mockito生成的实现更有意义)。除非您以其他方式存储它们,否则Mockito实现会返回零值或null所有实施的方法,所以试图拨打getClsThree,nullgetClsTwo返回导致NPE,直到你存根getClsTwo否则。

如果mockThree的存根在测试之间改变,您可以在初始化ClassFive之前将它们移动到您的测试中。我还坚持使用JUnit3语法和上面显式的initMocks,因为如果不使用Android Testing Support Library,Android Instrumentation测试会停留在JUnit3语法上;对于JUnit4或与该库的测试,您可以使用a cleaner alternative to initMocks。一旦你对Mockito感到满意,你也可以考虑RETURNS_DEEP_STUBS,但我喜欢保持我的存根清晰自己;该文件也正确地警告“每次模拟回归模拟,仙女死亡”。


这是不是很漫长而复杂,它不觉得没有必要吗? 是的。你周围侵犯Law of Demeter,它总结了维基百科(重点煤矿)工作作为:

  • 每个单元有关于其他单位只有有限的知识:只有单位“密切”关系到当前的单元。
  • 每个单位只能跟朋友交谈;不要与陌生人交谈。
  • 只与您的直接朋友交谈。

你的问题,并根据ClassThree,但只能通过ClassOne和ClassTwo实施细则从ClassFive您详细的解决方案都干。这不是一个严格的法律,但是在大学之外的自己的代码中,您可以将此视为一种标志,重新审视ClassOne,ClassTwo和ClassFive的设计以及它们之间的交互方式。如果ClassFive直接依赖于ClassThree,那么在生产和测试中使用代码可能会更容易,也许您会发现ClassOne根本就没有必要。

// ClassFive doesn't just work with its dependency ClassOne, it works directly with its 
// dependency's dependency's dependency ClassThree. 
super(..., p, 
    p.getClsTwo().getClsThree().loadFileFromAssetStore("Default value")); 
+0

表现的很出色,太感谢你了!你的解释绝对精彩。 –

+1

@PeterReid不客气!我很高兴它有帮助。祝你学业好,新年快乐! –

1

我想通过展示代码的样子来支持@JeffBowman的答案。

建议的解决方案意味着您将另一个参数添加到构造函数参数列表中,而且已经很长了。您的代码可以由青睐组成如下超过继承原则ClassFive构造的

大多数参数都是只有在那里被传递给父类的构造函数被简化。

在这种情况下,最好不要从那个超类继承,而是创建一个超级类的接口(例如:提取支持你的IDE)(让我们的调用是由SuperInterface实现的,超级类和CLassFive

的更换由SuperInterface类型的一个参数传递给超类的所有参数。

然后你直接委托不受CLassFive实施直接到的SuperInterface所有方法SuperInterface instance。

这是它会是什么样子:

public interface SuperInterface { 
    // all public methods of the super class. 
    } 

public class ClassFive implements SuperInterface{ 
    private final SuperInterface superClass; 
    private final Bitmap myBitmap 
    public ClassFive(SuperInterface superClass ,ClassTree p){ 
     this.superClass = superClass; 
     p.loadFileFromAssetStore("Default value")); 
     this.myBitmap = loadCorrectFile(...,p); 
    } 
    @Override 
    public void someMethodDeclaredInInterface(){ 
     this.superClass.someMethodDeclaredInInterface(); 
    } 
} 

这种模式也适用反之亦然,如果你不喜欢重复的方法代表团遍布延伸SuperInterface你的类。

如果您的专业化覆盖接口的几个方法并几乎完全相同,则此替代方法非常有用。

在这种情况下,您创建的接口可能不会由超类实现。在接口中声明的方法甚至不需要成为超类public方法的一部分。该接口只声明超类(现在应该更好地称为“泛型类”)需要使用派生行为的方法。

这应该是这样的:

interface AnimalSound{ 
    String get(); 
} 

class DogSound implements AnimalSound{ 
    @Override 
    public String get(){ 
    return "wouff"; 
    } 
} 

class CatSound implements AnimalSound{ 
    @Override 
    public String get(){ 
    return "meaw"; 
    } 
} 

class Animal { 
    private final AnimalSound sound; 
    public Animal(AnimalSound sound){ 
    this.sound = sound; 
    } 

    public String giveSound(){ 
    return sound.get(); 
    } 
} 

这是我们如何使用它:

List<Animal> animals = new ArrayList<>(); 
animals.add(new Animal(new DogSound())); 
animals.add(new Animal(new CatSound())); 
for(Animal animal : animals){ 
    System.out.println(animal.giveSound()); 
}