4

Which way should be better to use when one needs to test this program.
Firstly askUserPathAndWord() asks the user to input path and whatFind. We have two threads:

  • First thread scans folder and if it finds readable files put() it in the queue.
  • Second thread take()s from the queue and looks for whatFind into this file. If the search is successful it outputs to console the path of this file and the frequency of the word.

This is an integration dependence with multithreading work. Which variant is better able to test this program - Junit of EasyMock? I read some 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:

  • Which kind of test will have best coverage in this case?
  • How better to test contractual obligations in both variants?
  • If the answer is EasyMock, how should we do this correctly?
  • If Junit, how do we test void methods?
catch23
  • 17,519
  • 42
  • 144
  • 217
  • Sorry I cannot help you more than that, I don't have a practical knowledge of unit testing in Java. – didierc Feb 24 '13 at 16:27
  • 1
    JUnit is a unit test framework. EasyMock is a mocking framework. They're not the same thing. You typically use EasyMock inside JUnit tests. – JB Nizet Feb 24 '13 at 16:45
  • @JB Nizet How we can do test coverage here? Is need in this case EasyMock? – catch23 Feb 24 '13 at 17:04
  • 1
    Code coverage is yet something else. To measure code coverage, you use a tool like jacoco or cobertura, which will add instructions between every line of code in the generated byte code. You'll then execute the unit tests (or any other test, even manual tests), and some of the instructions inserted in the byte-code will be executed, allowing to know which ones were executed, and which weren't, thus measuring the code coverage. You could measure code coverage without using JUnit or EasyMock. For example, by playing with your app once instrumented with the chosen tool. – JB Nizet Feb 24 '13 at 17:12
  • Much offen is using EclEmma for check test coverage. – catch23 Feb 24 '13 at 17:17
  • Side comment, you while look would look a little cleaner with `while ((File file = queue.take()) != endOfWorkFile) { try {scan(file);} catch () { } }` – assylias Feb 26 '13 at 17:03
  • `queue.take()` schould be surround `try{`, `}catch` always – catch23 Feb 26 '13 at 20:07
  • You have asked 4 questions, not 1. – Raedwald Nov 11 '17 at 21:11
  • See also https://stackoverflow.com/questions/12159/how-should-i-unit-test-threaded-code – Raedwald Nov 12 '17 at 10:19

2 Answers2

2

JUnit or EasyMock? - the answer is, Both! One is a unit-test framework and the other allows mocking of objects, which enables you to write better tests. So, using JUnit plus any mocking framework (you suggest EasyMock, I use Mockito or there is a third one called jMock) is a great idea.

Which kind of test will have best coverage? - mocking allows you to focus the areas of code that you want to unit test, so you'll end up testing more of your code than before. It's especially useful to mock code that does heavy operations (like writing to a database or reading from a file system - like yours does). So using EasyMock with JUnit should give you better coverage (and better tests) than JUnit alone.

How better to test contractual obligations in both variants? - Make sure all your public methods are tested. Then, assert and verify your expectations thoroughly at the end of each test.

If the answer is EasyMock, how should we do this correctly? - use the verify method to check your mock object was called correctly during the test.

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

Good luck!

vikingsteve
  • 38,481
  • 23
  • 112
  • 156
  • `executor.execute(folderScan);` - Why do you think here is an error? – catch23 Feb 24 '13 at 18:30
  • My apologies, executor.execute should work. The rest of my answer, which I just editted, I hope is still useful to you. – vikingsteve Feb 24 '13 at 18:43
  • How we able to do JUnit test here with two threads? How schould be test `run()` method? And `void scan()` with JUnit – catch23 Feb 25 '13 at 11:01
  • You can easily test threading behaviour in a junit test. The run() method can be tested directly. I would split up the `scan()` method, in order to test it better. – vikingsteve Feb 26 '13 at 17:15
2

As 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 fit for use".

Keep in mind also that you're jumping in to the deep end a bit, because testing a multithreaded program is difficult. As much as possible, you should remove the complexity of the thread interaction from your testing of the functionality.

Looking at your program, you've got pretty good encapsulation and separation of concerns already, so you're well on your way. My strategy here would be to test both of the *Scan objects independently of threading. To do so, fix in your mind what their roles are. I would say the following:

  • FolderScan walks a directory structure, finding files of a particular type, and puts files passing the filter in to a queue. When it exhausts the directory structure, it puts a specific file in the queue, counts down a latch, and exits.
  • FileScan consumes a queue of files, does an operation on them, and prints the output to the console. When it hits a specific file, it counts down a latch and exits.

Since you already have presumably more or less working code, as you retrofit tests on to it (as opposed to writing them while writing the code, which is preferable) you want to change the source as little as possible in order to get to passing tests. After that you may wish to refactor the code, and your tests will give you confidence to do so.

FolderScan test

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

  1. A single folder with a file that passes the filter.
  2. A single folder with a file that does NOT pass the filter.
  3. A nested folder with a file in each.

The more granular the test, the better. Each test simply creates a new instance of FolderScan, gives it the arguments you've defined pointing at the given folder. Call run(), and make sure that:

  1. The queue contains the File objects you expect.
  2. CountDownLatch has been decremented.
  3. The last element in queue is the 'trigger' File.

FileScan test

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

  1. A file with a matching string
  2. A file without a matching string
  3. Multiple files

There is a problem with this class though, which is the classic 'how to test a Singleton' issue. The result of this object is actually piped to System.out, which you'd have to hook in to someway to check the result. As a first pass, I'd recommend refactoring the constructor to pass in a PrintStream. In production you'd pass in System.out, in test you'd pass in something that you can check: a new PrintStream(new ByteArrayOutputStream()) that you can check the contents of or, even better, a mock.

In the end, each test should check:

  1. The queue is emptied.
  2. CountDownLatch has been decremented
  3. The specified PrintStream has had the expected content written to it.

You should then have a great deal of confidence that both the FileScan and the FolderScan work as advertised.

* Testing askUserPathAndWord *

I don't see any straightforward way to test this function/class as written. It's violating the Single Responsibility Principle, and simply doing too many things. I would extract the following responsibilities in to new methods or classes:

  • Ask the user for word and path
  • Given a word, create a FileScan with the correct parameters (a Factory)
  • Given a path, create a FolderScan with the correct parameters (a Factory)
  • Given two runnables and a latch, create a ExecutorService, queue them, and wait on the latch

Then you can test them independently.

* Next steps *

One of the nice things about having these tests is that once you have them you can refactor freely, and be confident you haven't broken things. For example, you could look in to Callable instead of Runnable, which would let you deal with Future references, remove the output parameter from FolderScan, and remove the CountDownLatch entirely.

Happy testing!

sharakan
  • 6,821
  • 1
  • 34
  • 61
  • `FileScan` is not busy-waiting. `queue.take()` is a blocking call. – assylias Feb 26 '13 at 17:04
  • @sharakan How able to test `CountDownLatch`? – catch23 Feb 27 '13 at 06:40
  • 1
    Just use CountDownLatch.getCount(). In your test case you'll create a new latch with a initialCount. After run() returns, you should have assertEquals(initialCount-1, latch.getCount()). – sharakan Feb 27 '13 at 13:01
  • @sharakan How do make test `Executor` correctly? Is some point to test `run()` method or as each method? – catch23 Feb 28 '13 at 09:50
  • For testing the scanners, you should call run() from the testcase. No executor needed. – sharakan Feb 28 '13 at 12:05
  • @sharakan You recomend in `FileScan` to change `System.out` to `PrintStream`? Whether I understand right. – catch23 Mar 04 '13 at 11:53
  • More specifically, I recommend that you paramterize your output stream. So yes, FileScan should only know about a PrintStream, but you can pass in System.out from the caller. – sharakan Mar 04 '13 at 14:23
  • @sharakan how able to make in `FolderScan test` - a single folder with a file that passes the filter? how able to create in `test` body folders with files? how to realise this in test. – catch23 Mar 04 '13 at 19:01
  • In your code as written right now, to test it, you'd want to create a directory along with some contents and save them to the file system. Then pass the File object representing that directory to the `FolderScan` object. You should review the File javadocs for `File.createTempFile()` as a starting point. – sharakan Mar 04 '13 at 19:38