2012-07-02 26 views
7

我的任務是爲大量度量數據結構(即quadtreek-d tree變體)創建實現。我已經完成了大約四個這樣的實現,但我目前測試的方式並不是,因爲我沒有更好的詞彙,所以不錯。單元測試數據結構的內部狀態

我需要一種乾淨的方式來測試從這些樹/ trie結構中插入和刪除數據的方式,以便我可以測試節點的內部結構(檢查父母,子女,排序等)。這些實現遵循單獨的正確性證明和運行時分析,所以我需要確保不僅正確插入節點(意味着可以從樹中稍後檢索),還要確保樹中的「正確」位置。

然而,「單元測試」似乎是錯誤的方法,因爲如果我沒有弄錯,它的目的是測試一個結構或系統的外部API。我見過很多單元測試相關的問題,問我「如何在單元測試中訪問私有域」或「如何測試非公共方法的返回值」,答案通常是「不」 t「 - 我同意這個答案。

所以我不會讓任何人願意幫助只是含糊的隨筆,我的樹實現該接口是以下(基於關閉Java集合的地圖界面):

public interface SpatialMap<K, V> extends Iterable<SpatialMap.Entry<K, V>> 
{ 
// Query Operations 

/** 
* Returns the number of key-value mappings in this map. If the map contains more than 
* <tt>Integer.MAX_VALUE</tt> elements, returns <tt>Integer.MAX_VALUE</tt>. 
* 
* @return The number of key-value mappings in this map. 
*/ 
int size(); 

/** 
* Returns <tt>true</tt> if this map contains no key-value mappings. 
* 
* @return <tt>true</tt> if this map contains no key-value mappings. 
*/ 
boolean isEmpty(); 

/** 
* Returns <tt>true</tt> if this map contains a mapping for the specified key. 
* 
* @param key 
*   The key whose presence in this map is to be tested. 
* @return <tt>true</tt> if this map contains a mapping for the specified key. 
*/ 
boolean containsKey(K key); 

/** 
* Returns the value to which the specified key is mapped, or {@code null} if this map contains 
* no mapping for the key. 
* 
* <p>A return value of {@code null} does not <i>necessarily</i> indicate that the map contains 
* no mapping for the key; it's also possible that the map explicitly maps the key to 
* {@code null}. The {@link #containsKey containsKey} operation may be used to distinguish these 
* two cases. 
* 
* @see #put(Comparable, Comparable, Object) 
* 
* @param key 
*   The key whose associated value is to be returned. 
* @return The value to which the specified key is mapped, or {@code null} if this map contains 
*   no mapping for the key. 
*/ 
V get(K key); 

// Modification Operations 

/** 
* Associates the specified value with the specified key in this map. If the map previously 
* contained a mapping for the key, the old value is replaced. 
* 
* @param key 
*   The key with which the specified value is to be associated. 
* @param data 
*   The value to be associated with the specified key. 
* @return The previous value associated with the key, or <tt>null</tt> if there was no mapping 
*   for the key. (A <tt>null</tt> return can also indicate that the map previously 
*   associated <tt>null</tt> with <tt>key</tt>.) 
*/ 
V put(K key, V data); 

/** 
* Removes the mapping for the specified key from this map if present. 
* 
* @param key 
*   The key whose mapping is to be removed from the map. 
* @return The previous value associated with the key, or <tt>null</tt> if there was no mapping 
*   for the key. (A <tt>null</tt> return can also indicate that the map previously 
*   associated <tt>null</tt> with <tt>key</tt>.) 
*/ 
V remove(K key); 

// Bulk Operations 

/** 
* Removes all of the mappings from this map. The map will be empty after this call returns. 
*/ 
void clear(); 
} 

這使得很難測試只有公共方法,因爲我需要某些從公共接口不可用的數據(子/父指針)。另外,在樹結構(PR Quadtree,PRKDTree,MX變體等)中,節點與數據分離,因此創建一個返回「節點」的公共方法也將被抽象得太遠而無法獲得正確的數據。

我在找什麼類型的測試方法(或者我可以用於JUnit的技術,而不覺得我在摧毀美麗的認知邊界)?

回答

5

有些情況下,有時您確實需要測試結構的內部狀態。在這種情況下,我會使用反射訪問內部變量。有一些JUnit插件(PrivateAccessor http://junit-addons.sourceforge.net/junitx/util/PrivateAccessor.html)使這更容易。

折衷是因爲如果內部狀態發生變化,您的測試會更脆弱,那麼測試可能會中斷。但是如果你想要確信你的內部狀態是正確的,有時你需要這樣做。

+0

只要我的正確性證明實際上是正確的,節點排序的內部狀態應該永遠不會改變;) – efritz

+2

正確但是如果變量名稱改變,例如測試可能會中斷。這更像是一個白盒單元測試(與黑盒相對)。有時這種風格是適當的,聽起來就像你的情況,這是你在找什麼。 –

1

我在這種情況下使用的一種方法是使這些內部字段受到保護,並創建一個用於測試的子類。通過該子類,您可以公開您的白盒測試需要的任何狀態。

0

如果您將接口及其實現單獨放在專用包中,並使該實現的內部狀態方法爲受封裝保護的,那麼您的測試可以訪問它們並進行潛在的測試,而其餘的系統不能。

這對單元測試「純粹主義者」來說並不好,但通常我會這麼做,當我不想將我的課程的內容暴露給系統的其他部分時,但我仍然想做出斷言關於其內部行爲。