What is the best way to test a multi-threaded program with a common queue?

Which method is better to use when you need to test this program.
askUserPathAndWord() asks the user to enter path and whatFind . We have two threads:

  • The first line scans the folder, and if it finds readable put() files in the queue.
  • The second thread take() from the queue and looks for whatFind in this file. If the search succeeds, it displays on the console the path to this file and the frequency of the word.

This is an integration dependency with multi-threaded work. Which option is better to check this program - Junit of EasyMock? I read a few tutorials about EasyMock, but I don’t know in which cases it is better to use it.

Code:

 class FolderScan implements Runnable { private String path; private BlockingQueue<File> queue; private CountDownLatch latch; private File endOfWorkFile; FolderScan(String path, BlockingQueue<File> queue, CountDownLatch latch, File endOfWorkFile) { this.path = path; this.queue = queue; this.latch = latch; this.endOfWorkFile = endOfWorkFile; } public FolderScan() { } @Override public void run() { findFiles(path); queue.add(endOfWorkFile); latch.countDown(); } private void findFiles(String path) { try { File root = new File(path); File[] list = root.listFiles(); for (File currentFile : list) { String s = currentFile.getName().toLowerCase(); if (currentFile.isDirectory()) { findFiles(currentFile.getAbsolutePath()); } else { if (s.matches("^.*?\\.(txt|pdf|doc|docx|html|htm|xml|djvu|rar|rtf)$")) { queue.put(currentFile); } } } } catch (InterruptedException e) { e.printStackTrace(); } } } public class FileScan implements Runnable { private String whatFind; private BlockingQueue<File> queue; private CountDownLatch latch; private File endOfWorkFile; public FileScan(String whatFind, BlockingQueue<File> queue, CountDownLatch latch, File endOfWorkFile) { this.whatFind = whatFind; this.queue = queue; this.latch = latch; this.endOfWorkFile = endOfWorkFile; } public FileScan() { } @Override public void run() { while (true) { try { File file; file = queue.take(); if (file == endOfWorkFile) { break; } scan(file); } catch (InterruptedException e) { e.printStackTrace(); } } latch.countDown(); } private void scan(File file) { Scanner scanner = null; int matches = 0; try { scanner = new Scanner(file); } catch (FileNotFoundException e) { System.out.println("File Not Found."); e.printStackTrace(); } while (scanner.hasNext()) if (scanner.next().equals(whatFind)) { matches++; } if (matches > 0) { String myStr = String.format( "File: %s - and the number of matches " + "is: %d", file.getAbsolutePath(), matches); System.out.println(myStr); } } public void askUserPathAndWord() { BufferedReader bufferedReader = new BufferedReader( new InputStreamReader(System.in)); String path; String whatFind; BlockingQueue<File> queue = new LinkedBlockingQueue<File>(); try { System.out.println("Please, enter a Path and Word" + "(which you want to find):"); System.out.println("Please enter a Path:"); path = bufferedReader.readLine(); System.out.println("Please enter a Word:"); whatFind = bufferedReader.readLine(); if (path != null && whatFind != null) { File endOfWorkFile = new File("GameOver.tmp"); CountDownLatch latch = new CountDownLatch(2); FolderScan folderScan = new FolderScan(path, queue, latch, endOfWorkFile); FileScan fileScan = new FileScan(whatFind, queue, latch, endOfWorkFile); Executor executor = Executors.newCachedThreadPool(); executor.execute(folderScan); executor.execute(fileScan); latch.await(); System.out.println("Thank you!"); } else { System.out.println("You did not enter anything"); } } catch (IOException | RuntimeException e) { System.out.println("Wrong input!"); e.printStackTrace(); } catch (InterruptedException e) { System.out.println("Interrupted."); e.printStackTrace(); } } public static void main(String[] args) { new FileScan().askUserPathAndWord(); } } 

Questions:

  • What test will have the best coverage in this case?
  • What is the best way to check contractual obligations in both cases?
  • If the answer is EasyMock , how to do it right?
  • If Junit , how do we test void methods?
+4
source share
2 answers

What do you think about testing this program, keep in mind what unit testing is. From Wikipedia, "unit testing is a method by which individual units of source code are tested ... to determine if they are suitable for use."

Remember also that you are jumping a little to the deep end because testing a multi-threaded program is difficult. As much as possible, you should eliminate the complexity of the interaction of threads with testing functionality.

Looking at your program, you have a pretty good encapsulation and separation of problems, so that you are well on your way. My strategy here would be to *Scan both *Scan objects regardless of streaming. To do this, correct what their roles are. I would say the following:

  • FolderScan scans the directory structure, finds files of a certain type and puts the files that pass the filter to the queue. When it runs out of directory structures, it puts a specific file in the queue, counts the latch, and exits.
  • FileScan consumes a file queue, performs an operation on them, and outputs the output to the console. When it hits a specific file, it counts the latch and shuts down.

Since you already have more or less working code, since you modify tests for it (as opposed to writing them when writing code, which is preferable), you want to change the source as little as possible in order to get tested. After that, you may want to reorganize the code, and your tests will give you confidence in this.

FolderScan Test

As a first step, create a new JUnit test for FolderScan . You can write several tests, but from a high level, each of them should fill the directory structure with some files. I would check each of these cases at least:

  • One folder with the file that passes the filter.
  • One folder with a file that does NOT skip the filter.
  • A subfolder with a file in each.

The more whey the test, the better. Each test simply creates a new instance of FolderScan , gives it the arguments that you specified, pointing to this folder. Call run () and make sure:

  • The queue contains the File objects that you expect.
  • CountDownLatch decreased.
  • The last item in the queue is the β€œtrigger” File .

FileScan test

At a high level, the test for this should now be clear: create text File objects, fill the queue with them and a marker, and pass them to new FileScan objects. Again, the more granular, the better, at least:

  • File with corresponding line
  • File without corresponding line
  • Multiple files

There is a problem with this class, although this is a classic Singleton testing problem. The result of this object is actually transmitted through the System.out channel, which you will need to connect to check the result. As a first pass, I would recommend reorganizing the constructor for transfer to PrintStream . In the production process, you will go to System.out , in the test you will pass what you can check: a new PrintStream(new ByteArrayOutputStream()) , which you can check the contents or, even better, the layout.

In the end, each test should check:

  • The queue is down.
  • CountDownLatch reduced
  • The specified PrintStream has the expected content in it.

You should have a lot of confidence that both FileScan and FolderScan work as advertised.

* Testing askUserPathAndWord *

I see no easy way to test this function / class as written. This violates the Single Responsibility Principle and just does too many things. I would highlight the following responsibilities for new methods or classes:

  • Give the user a word and path
  • Based on the word, create a FileScan with the correct parameters (a Factory)
  • Set the path, create a FolderScan with the correct parameters (a Factory)
  • For two starts and latches, create an ExecutorService , leave them in the queue and wait on the latches

Then you can check them out yourself.

* Next steps *

One of the nice things about these tests is that, as soon as you have them, you can freely reorganize your documents and be sure that you have not violated. For example, you can look at Callable instead of Runnable , which allows you to access Future links, remove the output parameter from FolderScan and completely remove CountDownLatch .

Happy testing!

+2
source

JUnit or EasyMock? - Answer: Both! One of them is a unit test platform, and the other allows you to mock objects, which allows you to write better tests. So using JUnit plus any fake framework (you offer EasyMock , I use Mockito or there is a third called jMock ) is a great idea.

What kind of test will have the best coverage? - Bullying lets you focus on the areas of code that you want to unit test, so you'll end up testing more of your code than before. This is especially useful for falsifying code that performs heavy operations (for example, writing to a database or reading from a file system - just like yours). Therefore, using EasyMock with JUnit should give you better reach (and better tests) than just JUnit.

How much better is it to check contractual obligations in both options? - Make sure all your public methods are verified. Then, confirm and carefully check your expectations at the end of each test.

If the answer is EasyMock, how should we do it right? - use the verify method to verify that your layout was correctly called during the test.

If Junit, how do we test void methods? - you need some other way to claim that the results are expected. Perhaps you passed the object as a parameter to the void method, or maybe there is another way to get the result (e.g. getter).

Good luck

+2
source

All Articles