Android单元测试:我该如何测试



我正在测试一个android应用程序,并使用我所在大学提供的库,1-4班来自我的讲师供我们使用。

我有一个类的结构是这样的:

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);
}

ClassFour的结构如下:

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

我正在测试的Class是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?

这可以通过一些存根来轻松修复

@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等伪值,因此尝试在getClsTwo返回的null上调用getClsThree会导致NPE,直到您以其他方式存根getClsTwo

如果mockThree的存根在测试之间发生变化,您可以在初始化ClassFive之前将它们移动到测试中。我也坚持使用JUnit3语法和上面的显式initMocks,因为如果你不使用Android测试支持库,Android仪器测试就会坚持使用JU尼特3语法;对于JUnit4或该库的测试,您可以使用initMocks的更干净的替代方案。一旦你对Mockito感到满意,你也可以考虑RETURNS_DEEP_STUBS,但我喜欢自己保持我的存根明确;该文档还正确地警告"每次mock返回mock,就有一个仙女死亡"。


这不是很长很复杂吗,难道不觉得没有必要吗是的你正在处理违反德米特定律的行为,维基百科将其总结为(强调我的):

  • 每个单位对其他单位的了解应该有限:只有与当前单位"密切"相关的单位
  • 每个单位都应该只与自己的朋友交谈;不要和陌生人说话
  • 只与你的直系朋友交谈

您的问题和详细的解决方案都源于ClassFive,取决于ClassThree,但仅通过ClassOne和ClassTwo的实现细节。这不是一项严格的法律,但在大学之外的你自己的代码中,你可能会将其视为重新审视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"));

我想支持这个答案of@JeffBowman通过展示代码的外观。

所提出的解决方案意味着您将另一个参数添加到构造函数参数列表中,其长度已经很长了。您的代码可以通过遵循偏好组合而非继承原则来简化

ClassFive中构造函数的大多数参数只传递给父类构造函数。

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

将传递给超类的所有参数替换为一个类型为SuperInterface的参数。

然后,您只需将CLassFive未实现的SuperInterface的所有方法直接委托给SuperInterface实例。

这就是它的样子:

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的类中到处都有重复的方法委派,则此模式也可以反之亦然。

如果您的专业化只覆盖了接口的几个方法,而且几乎都覆盖了,那么这种替代方法是有用的。

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

这看起来像这样:

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());
}

相关内容

  • 没有找到相关文章

最新更新