Android单元测试:我如何测试?
我正在测试一个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?
这很容易通过一些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
,null
由getClsTwo
返回导致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"));
我想通过展示代码的样子来支持@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());
}
表现的很出色,太感谢你了!你的解释绝对精彩。 –
@PeterReid不客气!我很高兴它有帮助。祝你学业好,新年快乐! –