2013-04-26 11 views
40

Tôi có một giao diện List có triển khai bao gồm Danh sách được liên kết Singly, Doubly, Circular, vv Các bài kiểm tra đơn vị tôi đã viết cho Singly nên làm tốt cho hầu hết các Doubly cũng như Circular và bất kỳ triển khai mới nào khác của giao diện. Vì vậy, thay vì lặp lại các bài kiểm tra đơn vị cho mỗi lần thực hiện, JUnit có cung cấp một cái gì đó sẵn có mà sẽ cho phép tôi có một thử nghiệm JUnit và chạy nó với các triển khai khác nhau không?Viết một bài kiểm tra đơn vị để thực hiện nhiều giao diện

Sử dụng các kiểm tra tham số JUnit Tôi có thể cung cấp các triển khai khác nhau như Singly, doubly, circular etc nhưng đối với mỗi lần triển khai, cùng một đối tượng được sử dụng để thực thi tất cả các bài kiểm tra trong lớp.

+0

ý của bạn là "cùng một đối tượng được sử dụng để thực thi tất cả các thử nghiệm"? –

+0

Là một cựu người nghiện junit, tôi chỉ muốn nói rằng bạn nên nhìn vào groovy/spock. Spock rất tuyệt và hấp dẫn mang lại cho bạn một số khả năng mà bạn không thể làm với junit. Một trong những điều yêu thích của tôi là truy cập các thành viên dữ liệu riêng tư, do đó bạn không cần phải vạch trần thứ gì đó chỉ để tạo ra một bài kiểm tra đơn vị thích hợp. – Thom

Trả lời

31

Với JUnit 4.0 + bạn có thể sử dụng parameterized tests:

  • Thêm @RunWith(value = Parameterized.class) chú thích để vật cố thử nghiệm của bạn
  • Tạo một phương pháp public static trở Collection, chú thích nó với @Parameters, và đặt SinglyLinkedList.class, DoublyLinkedList.class, CircularList.class, vv . vào bộ sưu tập đó
  • Thêm người xây dựng vào lịch thi đấu của bạn mất Class: public MyListTest(Class cl) và lưu trữ Class trong một thể hiện biến listClass
  • Trong setUp phương pháp hoặc @Before, sử dụng List testList = (List)listClass.newInstance();

Với thiết lập ở trên tại chỗ, Á hậu tham số sẽ tạo ra một thể hiện mới của bộ ghép đo MyListTest của bạn cho mỗi lớp con mà bạn cung cấp trong phương thức @Parameters, cho phép bạn thực hiện cùng một logic thử nghiệm cho mọi lớp con mà bạn cần kiểm tra.

+0

Chết tiệt! Tại sao tôi không nghĩ về nó. Cảm ơn. – ChrisOdney

+0

Cách thực hiện ListList listList = (List) listClass.newInstance(); trong phương pháp setUp thay vì mọi phương pháp thử nghiệm? – ChrisOdney

+0

@ ChrisCdney Vâng, điều đó cũng có hiệu quả. Bạn cũng có thể tạo một phương thức 'makeList()' của trình trợ giúp trong trường hợp một số phương thức thử nghiệm của bạn phải tạo ra một số cá thể của lớp danh sách. – dasblinkenlight

42

tôi có lẽ muốn tránh kiểm tra tham số JUnit của (mà IMHO được thực hiện khá vụng về), và chỉ cần thực hiện một bản tóm tắt List lớp thử nghiệm có thể được thừa hưởng bởi các xét nghiệm triển khai:

public abstract class ListTestBase<T extends List> { 

    private T instance; 

    protected abstract T createInstance(); 

    @Before 
    public void setUp() { 
     instance = createInstance(); 
    } 

    @Test 
    public void testOneThing(){ /* ... */ } 

    @Test 
    public void testAnotherThing(){ /* ... */ } 

} 

Việc triển khai khác nhau sau đó nhận được của họ các lớp bê tông riêng:

class SinglyLinkedListTest extends ListTestBase<SinglyLinkedList> { 

    @Override 
    protected SinglyLinkedList createInstance(){ 
     return new SinglyLinkedList(); 
    } 

} 

class DoublyLinkedListTest extends ListTestBase<DoublyLinkedList> { 

    @Override 
    protected DoublyLinkedList createInstance(){ 
     return new DoublyLinkedList(); 
    } 

} 

Điều tốt đẹp về cách thực hiện theo cách này (thay vì tạo một lớp thử nghiệm để kiểm tra tất cả triển khai) là nếu bạn có một số trường hợp cụ thể thử nghiệm với một triển khai thực hiện, bạn chỉ có thể thêm nhiều thử nghiệm hơn vào lớp con kiểm tra cụ thể.

+2

Cảm ơn câu trả lời, nó thanh lịch hơn các bài kiểm tra của Junit Paramterized và tôi có thể sẽ sử dụng nó. Nhưng tôi phải gắn bó với câu trả lời của dasblinkenlight vì tôi đang tìm cách thoát ra bằng cách sử dụng các bài kiểm tra Parameterized của Junit – ChrisOdney

+1

Bạn có thể làm theo cách này, hoặc sử dụng các phép kiểm tra tham số và sử dụng Assume. Nếu có một phương pháp thử nghiệm chỉ áp dụng cho một (hoặc nhiều) lớp cụ thể, thì bạn sẽ có một giả định ở đầu bài kiểm tra, và bài kiểm tra đó sẽ bị bỏ qua cho các lớp khác. –

+0

Tôi nghĩ đây là cơ sở cho một giải pháp tuyệt vời. Điều quan trọng là có thể kiểm tra tất cả các phương thức giao diện được thực hiện bởi một đối tượng. Nhưng hãy tưởng tượng một giao diện RepositoryGateway với các phương thức như saveUser (user) và findUserById (id), nên được thực hiện cho các cơ sở dữ liệu khác nhau. Đối với findUserById (id), thiết lập cụ thể testmethod sẽ cần phải điền vào cơ sở dữ liệu cụ thể với người dùng được tìm thấy. Với saveUser (người dùng), phần xác nhận sẽ lấy dữ liệu từ cơ sở dữ liệu cụ thể. Điều này có thể được giải quyết bằng cách thêm một hook (như preparFindUser) trong testmethod, được thực thi bởi lớp test cụ thể không? –

0

Bạn thực sự có thể tạo phương thức trợ giúp trong lớp thử nghiệm để thiết lập thử nghiệm List của bạn là một ví dụ về một trong các triển khai của bạn phụ thuộc vào đối số. Kết hợp với this bạn sẽ có thể nhận được hành vi bạn muốn.

0

Mở rộng câu trả lời đầu tiên, các khía cạnh Tham số của JUnit4 hoạt động rất tốt. Đây là mã thực tế tôi đã sử dụng trong một bộ lọc kiểm tra dự án. Lớp được tạo bằng cách sử dụng chức năng của nhà máy (getPluginIO) và hàm getPluginsNamed nhận tất cả các lớp PluginInfo với tên sử dụng SezPoz và chú thích để cho phép các lớp mới được tự động phát hiện.

@RunWith(value=Parameterized.class) 
public class FilterTests { 
@Parameters 
public static Collection<PluginInfo[]> getPlugins() { 
    List<PluginInfo> possibleClasses=PluginManager.getPluginsNamed("Filter"); 
    return wrapCollection(possibleClasses); 
} 
final protected PluginInfo pluginId; 
final IOPlugin CFilter; 
public FilterTests(final PluginInfo pluginToUse) { 
    System.out.println("Using Plugin:"+pluginToUse); 
    pluginId=pluginToUse; // save plugin settings 
    CFilter=PluginManager.getPluginIO(pluginId); // create an instance using the factory 
} 
//.... the tests to run 

Lưu ý điều quan trọng là (Cá nhân tôi không có ý tưởng tại sao nó hoạt động theo cách này) để có bộ sưu tập như một tập hợp các mảng của tham số thực tế làm thức ăn cho các nhà xây dựng, trong trường hợp này một lớp được gọi là PluginInfo. Hàm tĩnh wrapCollection thực hiện nhiệm vụ này.

/** 
* Wrap a collection into a collection of arrays which is useful for parameterization in junit testing 
* @param inCollection input collection 
* @return wrapped collection 
*/ 
public static <T> Collection<T[]> wrapCollection(Collection<T> inCollection) { 
    final List<T[]> out=new ArrayList<T[]>(); 
    for(T curObj : inCollection) { 
     T[] arr = (T[])new Object[1]; 
     arr[0]=curObj; 
     out.add(arr); 
    } 
    return out; 
} 
1

Dựa trên anwser của @dasblinkenlightthis anwser tôi đã đưa ra một thực hiện đối với trường hợp sử dụng của tôi mà tôi muốn chia sẻ.

Tôi sử dụng ServiceProviderPattern (difference API and SPI) cho các lớp triển khai giao diện IImporterService. Nếu một triển khai mới của giao diện được phát triển, chỉ một tệp cấu hình trong số META-INF/services/ cần được thay đổi để đăng ký triển khai.

Tệp trong META-INF/services/ được đặt tên theo tên lớp đủ điều kiện của giao diện dịch vụ (IImporterService), ví dụ:

de.myapp.importer.IImporterService

Tập tin này có chứa một danh sách các Cassés mà thực hiện IImporterService, ví dụ

de.myapp.importer.impl.OfficeOpenXMLImporter

Lớp nhà máy ImporterFactory cung cấp cho khách hàng với việc triển khai cụ thể của giao diện.


Các ImporterFactory trả về một danh sách của tất cả các hiện thực của giao diện, đăng ký qua ServiceProviderPattern. Phương pháp setUp() đảm bảo rằng một cá thể mới được sử dụng cho từng trường hợp thử nghiệm.

@RunWith(Parameterized.class) 
public class IImporterServiceTest { 
    public IImporterService service; 

    public IImporterServiceTest(IImporterService service) { 
     this.service = service; 
    } 

    @Parameters 
    public static List<IImporterService> instancesToTest() { 
     return ImporterFactory.INSTANCE.getImplementations(); 
    } 

    @Before 
    public void setUp() throws Exception { 
     this.service = this.service.getClass().newInstance(); 
    } 

    @Test 
    public void testRead() { 
    } 
} 

Phương pháp ImporterFactory.INSTANCE.getImplementations() trông giống như sau:

public List<IImporterService> getImplementations() { 
    return (List<IImporterService>) GenericServiceLoader.INSTANCE.locateAll(IImporterService.class); 
} 
3

Tôi biết điều này là cũ, nhưng tôi đã học để làm điều này trong một biến thể hơi khác nhau mà hoạt động độc đáo trong đó bạn có thể áp dụng các @Parameter đến một thành viên trường để tiêm các giá trị.

Nó chỉ là một chút sạch hơn trong quan điểm của tôi.

@RunWith(Parameterized.class) 
public class MyTest{ 

    private ThingToTest subject; 

    @Parameter 
    public Class clazz; 

    @Parameters(name = "{index}: Impl Class: {0}") 
    public static Collection classes(){ 
     List<Object[]> implementations = new ArrayList<>(); 
     implementations.add(new Object[]{ImplementationOne.class}); 
     implementations.add(new Object[]{ImplementationTwo.class}); 

     return implementations; 
    } 

    @Before 
    public void setUp() throws Exception { 
     subject = (ThingToTest) clazz.getConstructor().newInstance(); 
    }