我正在尝试为依赖WifiManager和返回的ScanResults的几个类实现一些单元测试。我想做的是能够控制我收到的ScanResults,以便测试各种不同的条件。
不幸的是,我很难成功模拟WifiManager(虽然我想我可以在我的MockWifiManager中传递它的构造函数空引用)。这只是我的第一个问题,因为我有一个MockWifiManager可以玩(如果这甚至可以工作!)我将不得不成功创建我的测试ScanResults,它没有公共构造函数(想象一下它是由某个工厂创建的)。 / p>
问题: 如果它没有公共构造函数,我甚至可以扩展它吗?
我是否认为这一切都错了?我经常被问到如何做一个特定任务的问题,但实际上他们试图以错误的方式解决不同的问题,也许这就是我在这里做的事情?
我对android很新,所以不得不模拟所有这些功能一直试图说。
感谢您的投入!
编辑: 我也有一段时间来实例化一个MockWifiManager。 wifi管理器的构造函数期望IWifiManager是一种在Android SDK中似乎不存在的类型。
答案 0 :(得分:9)
围绕WifiManager创建一个抽象。用它来嘲笑你。嘲弄你不拥有的东西是硬而脆的。如果做得对,你应该能够切换内部,再加上你最终会得到一个更好的可模拟API。
对于您的测试,您可以根据您的内容存根/伪造经理。对于生产,您将传递一个具体的实例。
关于更改代码的观点,只是为了使其可测试不正确。首先,您应该模拟角色而不是类型,如下文所述。谷歌了解更多信息。
其次,围绕第三方代码创建抽象是最佳实践,如SOLID中的依赖性反转原则所述。无论您是否进行单元测试,都应该始终依赖于抽象而不是具体的实现。
http://www.objectmentor.com/resources/articles/dip.pdf http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod
答案 1 :(得分:2)
您可以尝试使用反射来访问私有构造函数来创建ScanResult实例。代码可能如下所示:
try {
Constructor<ScanResult> ctor = ScanResult.class.getDeclaredConstructor(null);
ctor.setAccessible(true);
ScanResult sr = ctor.newInstance(null);
sr.BSSID = "foo";
sr.SSID = "bar";
// etc...
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
对于其他测试方法,我大多数时候都会从ScanResult等实例转换信息,并仅将我需要的信息封装到我自己的对象中。这些是我努力工作的方法。这使得测试更容易,因为您可以轻松构建这些中间对象,而无需依赖真正的ScanResult对象。
答案 2 :(得分:0)
我一直在努力构建ScanResult
对象。我已经成功地使用了上面的反射方法。
如果有人正在寻找克隆ScanResult
对象(或实现Parcelable
接口的任何其他对象)的方法,你可以使用这种方法(我在单元测试中检查过它):
@RunWith(RobolectricTestRunner.class)
@Config(manifest=Config.NONE)
public class MovingAverageQueueTests {
@Test
public void parcelTest() {
Parcel parcel = Parcel.obtain();
ScanResult sr = buildScanResult("01:02:03:04:05:06", 70);
parcel.writeValue(sr);
parcel.setDataPosition(0); // required after unmarshalling
ScanResult clone = (ScanResult)parcel.readValue(ScanResult.class.getClassLoader());
parcel.recycle();
assertThat(clone.BSSID, is(equalTo(sr.BSSID)));
assertThat(clone.level, is(equalTo(sr.level)));
assertThat(clone, is(not(sameInstance(sr))));
}
private ScanResult buildScanResult(String mac, int level) {
Constructor<ScanResult> ctor = null;
ScanResult sr = null;
try {
ctor = ScanResult.class.getDeclaredConstructor(null);
ctor.setAccessible(true);
sr = ctor.newInstance(null);
sr.BSSID = mac;
sr.level = level;
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return sr;
}
}
至于表现,这个天真的检查:
@Test
public void buildVsClonePerformanceTest() {
ScanResult sr = null;
long start = System.nanoTime();
for (int i = 0; i < 1000000; i++) {
sr = buildScanResult("01:02:03:04:05:06", 70);
}
long elapsedNanos = System.nanoTime() - start;
LOGGER.info("buildScanResult: " + elapsedNanos);
start = System.nanoTime();
for (int i = 0; i < 1000000; i++) {
sr = cloneScanResult(sr);
}
elapsedNanos = System.nanoTime() - start;
LOGGER.info("cloneScanResult: " + elapsedNanos);
}
显示以下结果:
2016年10月26日下午3:25:19 com.example.neutrino.maze.MovingAverageQueueTests buildVsClonePerformanceTest 信息:buildScanResult: 202072179 2016年10月26日下午3:25:21 com.example.neutrino.maze.MovingAverageQueueTests buildVsClonePerformanceTest 信息:cloneScanResult: 2004391903
所以克隆这种方式的效果比使用反射创建实例要低10倍。我知道这个测试并不健全,因为在编译时进行了优化......但是10的因子很难减轻。我也测试了10K迭代,然后因子甚至是100!仅供参考。
P.S。让Parcel.obtain()和parcel.recycle脱离循环无济于事